Refactor: implemented robust structured JSON parser for Moodle questions and DOM scope detection
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,66 @@
|
||||
<div id="question-125-7" class="que essay manualgraded notyetanswered">
|
||||
<div class="info">
|
||||
<h3 class="no">Question <span class="qno">7</span></h3>
|
||||
<div class="state">Not yet answered</div>
|
||||
<div class="grade">Marked out of 1.00</div>
|
||||
<div class="questionflag editable" id="yui_3_18_1_1_1775909302600_54">
|
||||
<input type="hidden" name="q125:7_:flagged" value="0" /><input
|
||||
type="hidden"
|
||||
value="qaid=804&qubaid=125&qid=3704&slot=7&checksum=4717bc6928ebf1e9a66ab68e7e595899&sesskey=MRh4T7j2fU&newstate="
|
||||
class="questionflagpostdata"
|
||||
/>
|
||||
<input
|
||||
type="hidden"
|
||||
class="questionflagvalue"
|
||||
id="q125:7_:flaggedcheckbox"
|
||||
name="q125:7_:flagged"
|
||||
value="0"
|
||||
/><a
|
||||
tabindex="0"
|
||||
class="aabtn"
|
||||
role="button"
|
||||
aria-pressed="false"
|
||||
aria-label="Flagged"
|
||||
title="Flag this question for future reference"
|
||||
><img
|
||||
class="questionflagimage"
|
||||
src="https://school.moodledemo.net/theme/image.php/boost/core/1775905339/i/unflagged"
|
||||
alt=""
|
||||
/>Flag question</a
|
||||
>
|
||||
</div>
|
||||
<div class="editquestion">
|
||||
<a
|
||||
href="https://school.moodledemo.net/question/bank/editquestion/question.php?cmid=1655&returnurl=%2Fmod%2Fquiz%2Fattempt.php%3Fattempt%3D89%26cmid%3D1655%23question-125-7&id=3704"
|
||||
><i class="icon fa fa-pen fa-fw iconsmall" title="Edit" role="img" aria-label="Edit"></i
|
||||
>Edit question</a
|
||||
>
|
||||
</div>
|
||||
<span class="badge bg-primary text-light">v1 (latest)</span>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="formulation clearfix">
|
||||
<h4 class="accesshide">Question text</h4>
|
||||
<input type="hidden" name="q125:7_:sequencecheck" value="1" />
|
||||
<div class="qtext" style="cursor: pointer">
|
||||
<div class="clearfix">
|
||||
<p>What is resilience?</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ablock">
|
||||
<div class="answer">
|
||||
<label class="visually-hidden" for="q125:7_answer_id">Answer text Question 7</label
|
||||
><textarea
|
||||
name="q125:7_answer"
|
||||
id="q125:7_answer_id"
|
||||
class="qtype_essay_plain qtype_essay_response form-control"
|
||||
rows="10"
|
||||
cols="60"
|
||||
></textarea
|
||||
><input type="hidden" name="q125:7_answerformat" value="2" />
|
||||
</div>
|
||||
<div class="attachments"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,753 @@
|
||||
<div id="question-125-6" class="que essay manualgraded notyetanswered">
|
||||
<div class="info">
|
||||
<h3 class="no">Question <span class="qno">6</span></h3>
|
||||
<div class="state">Not yet answered</div>
|
||||
<div class="grade">Marked out of 1.00</div>
|
||||
<div class="questionflag editable" id="yui_3_18_1_1_1775909302600_48">
|
||||
<input type="hidden" name="q125:6_:flagged" value="0" /><input
|
||||
type="hidden"
|
||||
value="qaid=803&qubaid=125&qid=3703&slot=6&checksum=3aadede34be020a7858cc02258ea5877&sesskey=MRh4T7j2fU&newstate="
|
||||
class="questionflagpostdata"
|
||||
/>
|
||||
<input
|
||||
type="hidden"
|
||||
class="questionflagvalue"
|
||||
id="q125:6_:flaggedcheckbox"
|
||||
name="q125:6_:flagged"
|
||||
value="0"
|
||||
/><a
|
||||
tabindex="0"
|
||||
class="aabtn"
|
||||
role="button"
|
||||
aria-pressed="false"
|
||||
aria-label="Flagged"
|
||||
title="Flag this question for future reference"
|
||||
><img
|
||||
class="questionflagimage"
|
||||
src="https://school.moodledemo.net/theme/image.php/boost/core/1775905339/i/unflagged"
|
||||
alt=""
|
||||
/>Flag question</a
|
||||
>
|
||||
</div>
|
||||
<div class="editquestion">
|
||||
<a
|
||||
href="https://school.moodledemo.net/question/bank/editquestion/question.php?cmid=1655&returnurl=%2Fmod%2Fquiz%2Fattempt.php%3Fattempt%3D89%26cmid%3D1655%23question-125-6&id=3703"
|
||||
><i class="icon fa fa-pen fa-fw iconsmall" title="Edit" role="img" aria-label="Edit"></i
|
||||
>Edit question</a
|
||||
>
|
||||
</div>
|
||||
<span class="badge bg-primary text-light">v1 (latest)</span>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="formulation clearfix">
|
||||
<h4 class="accesshide">Question text</h4>
|
||||
<input type="hidden" name="q125:6_:sequencecheck" value="1" />
|
||||
<div class="qtext" style="cursor: pointer">
|
||||
<div class="clearfix">
|
||||
<p>What is resilience?</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ablock">
|
||||
<div class="answer">
|
||||
<label class="visually-hidden" for="q125:6_answer_id">Answer text Question 6</label>
|
||||
<div class="qtype_essay_editor qtype_essay_response">
|
||||
<div>
|
||||
<textarea
|
||||
id="q125:6_answer_id"
|
||||
name="q125:6_answer"
|
||||
rows="10"
|
||||
cols="60"
|
||||
class="form-control"
|
||||
style="display: none"
|
||||
aria-hidden="true"
|
||||
data-fieldtype="editor"
|
||||
></textarea>
|
||||
<div
|
||||
role="application"
|
||||
class="tox tox-tinymce"
|
||||
aria-disabled="false"
|
||||
style="visibility: hidden; height: 237px"
|
||||
>
|
||||
<div class="tox-editor-container">
|
||||
<div data-alloy-vertical-dir="toptobottom" class="tox-editor-header">
|
||||
<div role="menubar" data-alloy-tabstop="true" class="tox-menubar">
|
||||
<button
|
||||
aria-haspopup="true"
|
||||
role="menuitem"
|
||||
type="button"
|
||||
tabindex="-1"
|
||||
data-alloy-tabstop="true"
|
||||
unselectable="on"
|
||||
class="tox-mbtn tox-mbtn--select"
|
||||
aria-expanded="false"
|
||||
style="user-select: none; width: 39.4688px"
|
||||
>
|
||||
<span class="tox-mbtn__select-label">Edit</span>
|
||||
<div class="tox-mbtn__select-chevron">
|
||||
<svg width="10" height="10" focusable="false">
|
||||
<path
|
||||
d="M8.7 2.2c.3-.3.8-.3 1 0 .4.4.4.9 0 1.2L5.7 7.8c-.3.3-.9.3-1.2 0L.2 3.4a.8.8 0 0 1 0-1.2c.3-.3.8-.3 1.1 0L5 6l3.7-3.8Z"
|
||||
fill-rule="nonzero"
|
||||
></path>
|
||||
</svg>
|
||||
</div></button
|
||||
><button
|
||||
aria-haspopup="true"
|
||||
role="menuitem"
|
||||
type="button"
|
||||
tabindex="-1"
|
||||
data-alloy-tabstop="true"
|
||||
unselectable="on"
|
||||
class="tox-mbtn tox-mbtn--select"
|
||||
style="user-select: none; width: 45.5312px"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<span class="tox-mbtn__select-label">View</span>
|
||||
<div class="tox-mbtn__select-chevron">
|
||||
<svg width="10" height="10" focusable="false">
|
||||
<path
|
||||
d="M8.7 2.2c.3-.3.8-.3 1 0 .4.4.4.9 0 1.2L5.7 7.8c-.3.3-.9.3-1.2 0L.2 3.4a.8.8 0 0 1 0-1.2c.3-.3.8-.3 1.1 0L5 6l3.7-3.8Z"
|
||||
fill-rule="nonzero"
|
||||
></path>
|
||||
</svg>
|
||||
</div></button
|
||||
><button
|
||||
aria-haspopup="true"
|
||||
role="menuitem"
|
||||
type="button"
|
||||
tabindex="-1"
|
||||
data-alloy-tabstop="true"
|
||||
unselectable="on"
|
||||
class="tox-mbtn tox-mbtn--select"
|
||||
style="user-select: none; width: 50.5312px"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<span class="tox-mbtn__select-label">Insert</span>
|
||||
<div class="tox-mbtn__select-chevron">
|
||||
<svg width="10" height="10" focusable="false">
|
||||
<path
|
||||
d="M8.7 2.2c.3-.3.8-.3 1 0 .4.4.4.9 0 1.2L5.7 7.8c-.3.3-.9.3-1.2 0L.2 3.4a.8.8 0 0 1 0-1.2c.3-.3.8-.3 1.1 0L5 6l3.7-3.8Z"
|
||||
fill-rule="nonzero"
|
||||
></path>
|
||||
</svg>
|
||||
</div></button
|
||||
><button
|
||||
aria-haspopup="true"
|
||||
role="menuitem"
|
||||
type="button"
|
||||
tabindex="-1"
|
||||
data-alloy-tabstop="true"
|
||||
unselectable="on"
|
||||
class="tox-mbtn tox-mbtn--select"
|
||||
style="user-select: none; width: 59.8438px"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<span class="tox-mbtn__select-label">Format</span>
|
||||
<div class="tox-mbtn__select-chevron">
|
||||
<svg width="10" height="10" focusable="false">
|
||||
<path
|
||||
d="M8.7 2.2c.3-.3.8-.3 1 0 .4.4.4.9 0 1.2L5.7 7.8c-.3.3-.9.3-1.2 0L.2 3.4a.8.8 0 0 1 0-1.2c.3-.3.8-.3 1.1 0L5 6l3.7-3.8Z"
|
||||
fill-rule="nonzero"
|
||||
></path>
|
||||
</svg>
|
||||
</div></button
|
||||
><button
|
||||
aria-haspopup="true"
|
||||
role="menuitem"
|
||||
type="button"
|
||||
tabindex="-1"
|
||||
data-alloy-tabstop="true"
|
||||
unselectable="on"
|
||||
class="tox-mbtn tox-mbtn--select"
|
||||
style="user-select: none; width: 47.7188px"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<span class="tox-mbtn__select-label">Tools</span>
|
||||
<div class="tox-mbtn__select-chevron">
|
||||
<svg width="10" height="10" focusable="false">
|
||||
<path
|
||||
d="M8.7 2.2c.3-.3.8-.3 1 0 .4.4.4.9 0 1.2L5.7 7.8c-.3.3-.9.3-1.2 0L.2 3.4a.8.8 0 0 1 0-1.2c.3-.3.8-.3 1.1 0L5 6l3.7-3.8Z"
|
||||
fill-rule="nonzero"
|
||||
></path>
|
||||
</svg>
|
||||
</div></button
|
||||
><button
|
||||
aria-haspopup="true"
|
||||
role="menuitem"
|
||||
type="button"
|
||||
tabindex="-1"
|
||||
data-alloy-tabstop="true"
|
||||
unselectable="on"
|
||||
class="tox-mbtn tox-mbtn--select"
|
||||
style="user-select: none; width: 47.8281px"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<span class="tox-mbtn__select-label">Table</span>
|
||||
<div class="tox-mbtn__select-chevron">
|
||||
<svg width="10" height="10" focusable="false">
|
||||
<path
|
||||
d="M8.7 2.2c.3-.3.8-.3 1 0 .4.4.4.9 0 1.2L5.7 7.8c-.3.3-.9.3-1.2 0L.2 3.4a.8.8 0 0 1 0-1.2c.3-.3.8-.3 1.1 0L5 6l3.7-3.8Z"
|
||||
fill-rule="nonzero"
|
||||
></path>
|
||||
</svg>
|
||||
</div></button
|
||||
><button
|
||||
aria-haspopup="true"
|
||||
role="menuitem"
|
||||
type="button"
|
||||
tabindex="-1"
|
||||
data-alloy-tabstop="true"
|
||||
unselectable="on"
|
||||
class="tox-mbtn tox-mbtn--select"
|
||||
style="user-select: none; width: 44.8906px"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<span class="tox-mbtn__select-label">Help</span>
|
||||
<div class="tox-mbtn__select-chevron">
|
||||
<svg width="10" height="10" focusable="false">
|
||||
<path
|
||||
d="M8.7 2.2c.3-.3.8-.3 1 0 .4.4.4.9 0 1.2L5.7 7.8c-.3.3-.9.3-1.2 0L.2 3.4a.8.8 0 0 1 0-1.2c.3-.3.8-.3 1.1 0L5 6l3.7-3.8Z"
|
||||
fill-rule="nonzero"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<div role="group" class="tox-toolbar-overlord" aria-disabled="false">
|
||||
<div role="group" class="tox-toolbar__primary">
|
||||
<div
|
||||
aria-label="history"
|
||||
role="toolbar"
|
||||
data-alloy-tabstop="true"
|
||||
class="tox-toolbar__group"
|
||||
>
|
||||
<button
|
||||
aria-label="Undo"
|
||||
data-mce-name="undo"
|
||||
type="button"
|
||||
tabindex="-1"
|
||||
class="tox-tbtn tox-tbtn--disabled"
|
||||
aria-disabled="true"
|
||||
style="width: 34px"
|
||||
>
|
||||
<span class="tox-icon tox-tbtn__icon-wrap"
|
||||
><svg width="24" height="24" focusable="false">
|
||||
<path
|
||||
d="M6.4 8H12c3.7 0 6.2 2 6.8 5.1.6 2.7-.4 5.6-2.3 6.8a1 1 0 0 1-1-1.8c1.1-.6 1.8-2.7 1.4-4.6-.5-2.1-2.1-3.5-4.9-3.5H6.4l3.3 3.3a1 1 0 1 1-1.4 1.4l-5-5a1 1 0 0 1 0-1.4l5-5a1 1 0 0 1 1.4 1.4L6.4 8Z"
|
||||
fill-rule="nonzero"
|
||||
></path></svg
|
||||
></span></button
|
||||
><button
|
||||
aria-label="Redo"
|
||||
data-mce-name="redo"
|
||||
type="button"
|
||||
tabindex="-1"
|
||||
class="tox-tbtn tox-tbtn--disabled"
|
||||
aria-disabled="true"
|
||||
style="width: 34px"
|
||||
>
|
||||
<span class="tox-icon tox-tbtn__icon-wrap"
|
||||
><svg width="24" height="24" focusable="false">
|
||||
<path
|
||||
d="M17.6 10H12c-2.8 0-4.4 1.4-4.9 3.5-.4 2 .3 4 1.4 4.6a1 1 0 1 1-1 1.8c-2-1.2-2.9-4.1-2.3-6.8.6-3 3-5.1 6.8-5.1h5.6l-3.3-3.3a1 1 0 1 1 1.4-1.4l5 5a1 1 0 0 1 0 1.4l-5 5a1 1 0 0 1-1.4-1.4l3.3-3.3Z"
|
||||
fill-rule="nonzero"
|
||||
></path></svg
|
||||
></span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
aria-label="formatting"
|
||||
role="toolbar"
|
||||
data-alloy-tabstop="true"
|
||||
class="tox-toolbar__group"
|
||||
>
|
||||
<button
|
||||
aria-label="Bold"
|
||||
data-mce-name="bold"
|
||||
type="button"
|
||||
tabindex="-1"
|
||||
class="tox-tbtn"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
style="width: 34px"
|
||||
>
|
||||
<span class="tox-icon tox-tbtn__icon-wrap"
|
||||
><svg width="24" height="24" focusable="false">
|
||||
<path
|
||||
d="M7.8 19c-.3 0-.5 0-.6-.2l-.2-.5V5.7c0-.2 0-.4.2-.5l.6-.2h5c1.5 0 2.7.3 3.5 1 .7.6 1.1 1.4 1.1 2.5a3 3 0 0 1-.6 1.9c-.4.6-1 1-1.6 1.2.4.1.9.3 1.3.6s.8.7 1 1.2c.4.4.5 1 .5 1.6 0 1.3-.4 2.3-1.3 3-.8.7-2.1 1-3.8 1H7.8Zm5-8.3c.6 0 1.2-.1 1.6-.5.4-.3.6-.7.6-1.3 0-1.1-.8-1.7-2.3-1.7H9.3v3.5h3.4Zm.5 6c.7 0 1.3-.1 1.7-.4.4-.4.6-.9.6-1.5s-.2-1-.7-1.4c-.4-.3-1-.4-2-.4H9.4v3.8h4Z"
|
||||
fill-rule="evenodd"
|
||||
></path></svg
|
||||
></span></button
|
||||
><button
|
||||
aria-label="Italic"
|
||||
data-mce-name="italic"
|
||||
type="button"
|
||||
tabindex="-1"
|
||||
class="tox-tbtn"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
style="width: 34px"
|
||||
>
|
||||
<span class="tox-icon tox-tbtn__icon-wrap"
|
||||
><svg width="24" height="24" focusable="false">
|
||||
<path
|
||||
d="m16.7 4.7-.1.9h-.3c-.6 0-1 0-1.4.3-.3.3-.4.6-.5 1.1l-2.1 9.8v.6c0 .5.4.8 1.4.8h.2l-.2.8H8l.2-.8h.2c1.1 0 1.8-.5 2-1.5l2-9.8.1-.5c0-.6-.4-.8-1.4-.8h-.3l.2-.9h5.8Z"
|
||||
fill-rule="evenodd"
|
||||
></path></svg
|
||||
></span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
aria-label="content"
|
||||
role="toolbar"
|
||||
data-alloy-tabstop="true"
|
||||
class="tox-toolbar__group"
|
||||
>
|
||||
<button
|
||||
aria-label="Insert H5P content"
|
||||
data-mce-name="tiny_h5p"
|
||||
type="button"
|
||||
tabindex="-1"
|
||||
class="tox-tbtn"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
style="width: 34px"
|
||||
>
|
||||
<span class="tox-icon tox-tbtn__icon-wrap"
|
||||
><svg
|
||||
data-buttonsource="moodle"
|
||||
width="24"
|
||||
height="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
focusable="false"
|
||||
>
|
||||
<image
|
||||
href="https://school.moodledemo.net/theme/image.php/boost/tiny_h5p/1775905339/icon"
|
||||
width="24"
|
||||
height="24"
|
||||
></image></svg
|
||||
></span></button
|
||||
><button
|
||||
aria-label="Link"
|
||||
data-mce-name="tiny_link_link"
|
||||
type="button"
|
||||
tabindex="-1"
|
||||
class="tox-tbtn"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
style="width: 34px"
|
||||
>
|
||||
<span class="tox-icon tox-tbtn__icon-wrap"
|
||||
><svg width="24" height="24" focusable="false">
|
||||
<path
|
||||
d="M6.2 12.3a1 1 0 0 1 1.4 1.4l-2 2a2 2 0 1 0 2.6 2.8l4.8-4.8a1 1 0 0 0 0-1.4 1 1 0 1 1 1.4-1.3 2.9 2.9 0 0 1 0 4L9.6 20a3.9 3.9 0 0 1-5.5-5.5l2-2Zm11.6-.6a1 1 0 0 1-1.4-1.4l2-2a2 2 0 1 0-2.6-2.8L11 10.3a1 1 0 0 0 0 1.4A1 1 0 1 1 9.6 13a2.9 2.9 0 0 1 0-4L14.4 4a3.9 3.9 0 0 1 5.5 5.5l-2 2Z"
|
||||
fill-rule="nonzero"
|
||||
></path></svg
|
||||
></span></button
|
||||
><button
|
||||
aria-label="Unlink"
|
||||
data-mce-name="tiny_link_unlink"
|
||||
type="button"
|
||||
tabindex="-1"
|
||||
class="tox-tbtn"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
style="width: 34px"
|
||||
>
|
||||
<span class="tox-icon tox-tbtn__icon-wrap"
|
||||
><svg width="24" height="24" focusable="false">
|
||||
<path
|
||||
d="M6.2 12.3a1 1 0 0 1 1.4 1.4l-2 2a2 2 0 1 0 2.6 2.8l4.8-4.8a1 1 0 0 0 0-1.4 1 1 0 1 1 1.4-1.3 2.9 2.9 0 0 1 0 4L9.6 20a3.9 3.9 0 0 1-5.5-5.5l2-2Zm11.6-.6a1 1 0 0 1-1.4-1.4l2.1-2a2 2 0 1 0-2.7-2.8L11 10.3a1 1 0 0 0 0 1.4A1 1 0 1 1 9.6 13a2.9 2.9 0 0 1 0-4L14.4 4a3.9 3.9 0 0 1 5.5 5.5l-2 2ZM7.6 6.3a.8.8 0 0 1-1 1.1L3.3 4.2a.7.7 0 1 1 1-1l3.2 3.1ZM5.1 8.6a.8.8 0 0 1 0 1.5H3a.8.8 0 0 1 0-1.5H5Zm5-3.5a.8.8 0 0 1-1.5 0V3a.8.8 0 0 1 1.5 0V5Zm6 11.8a.8.8 0 0 1 1-1l3.2 3.2a.8.8 0 0 1-1 1L16 17Zm-2.2 2a.8.8 0 0 1 1.5 0V21a.8.8 0 0 1-1.5 0V19Zm5-3.5a.7.7 0 1 1 0-1.5H21a.8.8 0 0 1 0 1.5H19Z"
|
||||
fill-rule="nonzero"
|
||||
></path></svg
|
||||
></span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
aria-label="view"
|
||||
role="toolbar"
|
||||
data-alloy-tabstop="true"
|
||||
class="tox-toolbar__group"
|
||||
>
|
||||
<button
|
||||
aria-label="Fullscreen"
|
||||
data-mce-name="fullscreen"
|
||||
type="button"
|
||||
tabindex="-1"
|
||||
class="tox-tbtn"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
style="width: 34px"
|
||||
>
|
||||
<span class="tox-icon tox-tbtn__icon-wrap"
|
||||
><svg width="24" height="24" focusable="false">
|
||||
<path
|
||||
d="m15.3 10-1.2-1.3 2.9-3h-2.3a.9.9 0 1 1 0-1.7H19c.5 0 .9.4.9.9v4.4a.9.9 0 1 1-1.8 0V7l-2.9 3Zm0 4 3 3v-2.3a.9.9 0 1 1 1.7 0V19c0 .5-.4.9-.9.9h-4.4a.9.9 0 1 1 0-1.8H17l-3-2.9 1.3-1.2ZM10 15.4l-2.9 3h2.3a.9.9 0 1 1 0 1.7H5a.9.9 0 0 1-.9-.9v-4.4a.9.9 0 1 1 1.8 0V17l2.9-3 1.2 1.3ZM8.7 10 5.7 7v2.3a.9.9 0 0 1-1.7 0V5c0-.5.4-.9.9-.9h4.4a.9.9 0 0 1 0 1.8H7l3 2.9-1.3 1.2Z"
|
||||
fill-rule="nonzero"
|
||||
></path></svg
|
||||
></span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
aria-label="alignment"
|
||||
role="toolbar"
|
||||
data-alloy-tabstop="true"
|
||||
class="tox-toolbar__group"
|
||||
>
|
||||
<button
|
||||
aria-label="Align left"
|
||||
data-mce-name="alignleft"
|
||||
type="button"
|
||||
tabindex="-1"
|
||||
class="tox-tbtn"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
style="width: 34px"
|
||||
>
|
||||
<span class="tox-icon tox-tbtn__icon-wrap"
|
||||
><svg width="24" height="24" focusable="false">
|
||||
<path
|
||||
d="M5 5h14c.6 0 1 .4 1 1s-.4 1-1 1H5a1 1 0 1 1 0-2Zm0 4h8c.6 0 1 .4 1 1s-.4 1-1 1H5a1 1 0 1 1 0-2Zm0 8h8c.6 0 1 .4 1 1s-.4 1-1 1H5a1 1 0 0 1 0-2Zm0-4h14c.6 0 1 .4 1 1s-.4 1-1 1H5a1 1 0 0 1 0-2Z"
|
||||
fill-rule="evenodd"
|
||||
></path></svg
|
||||
></span></button
|
||||
><button
|
||||
aria-label="Align centre"
|
||||
data-mce-name="aligncenter"
|
||||
type="button"
|
||||
tabindex="-1"
|
||||
class="tox-tbtn"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
style="width: 34px"
|
||||
>
|
||||
<span class="tox-icon tox-tbtn__icon-wrap"
|
||||
><svg width="24" height="24" focusable="false">
|
||||
<path
|
||||
d="M5 5h14c.6 0 1 .4 1 1s-.4 1-1 1H5a1 1 0 1 1 0-2Zm3 4h8c.6 0 1 .4 1 1s-.4 1-1 1H8a1 1 0 1 1 0-2Zm0 8h8c.6 0 1 .4 1 1s-.4 1-1 1H8a1 1 0 0 1 0-2Zm-3-4h14c.6 0 1 .4 1 1s-.4 1-1 1H5a1 1 0 0 1 0-2Z"
|
||||
fill-rule="evenodd"
|
||||
></path></svg
|
||||
></span></button
|
||||
><button
|
||||
aria-label="Align right"
|
||||
data-mce-name="alignright"
|
||||
type="button"
|
||||
tabindex="-1"
|
||||
class="tox-tbtn"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
style="width: 34px"
|
||||
>
|
||||
<span class="tox-icon tox-tbtn__icon-wrap"
|
||||
><svg width="24" height="24" focusable="false">
|
||||
<path
|
||||
d="M5 5h14c.6 0 1 .4 1 1s-.4 1-1 1H5a1 1 0 1 1 0-2Zm6 4h8c.6 0 1 .4 1 1s-.4 1-1 1h-8a1 1 0 0 1 0-2Zm0 8h8c.6 0 1 .4 1 1s-.4 1-1 1h-8a1 1 0 0 1 0-2Zm-6-4h14c.6 0 1 .4 1 1s-.4 1-1 1H5a1 1 0 0 1 0-2Z"
|
||||
fill-rule="evenodd"
|
||||
></path></svg
|
||||
></span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
aria-label="directionality"
|
||||
role="toolbar"
|
||||
data-alloy-tabstop="true"
|
||||
class="tox-toolbar__group"
|
||||
>
|
||||
<button
|
||||
aria-label="Left to right"
|
||||
data-mce-name="ltr"
|
||||
type="button"
|
||||
tabindex="-1"
|
||||
class="tox-tbtn tox-tbtn--enabled"
|
||||
aria-disabled="false"
|
||||
aria-pressed="true"
|
||||
style="width: 34px"
|
||||
>
|
||||
<span class="tox-icon tox-tbtn__icon-wrap"
|
||||
><svg width="24" height="24" focusable="false">
|
||||
<path
|
||||
d="M11 5h7a1 1 0 0 1 0 2h-1v11a1 1 0 0 1-2 0V7h-2v11a1 1 0 0 1-2 0v-6c-.5 0-1 0-1.4-.3A3.4 3.4 0 0 1 7.8 10a3.3 3.3 0 0 1 0-2.8 3.4 3.4 0 0 1 1.8-1.8L11 5ZM4.4 16.2 6.2 15l-1.8-1.2a1 1 0 0 1 1.2-1.6l3 2a1 1 0 0 1 0 1.6l-3 2a1 1 0 1 1-1.2-1.6Z"
|
||||
fill-rule="evenodd"
|
||||
></path></svg
|
||||
></span></button
|
||||
><button
|
||||
aria-label="Right to left"
|
||||
data-mce-name="rtl"
|
||||
type="button"
|
||||
tabindex="-1"
|
||||
class="tox-tbtn"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
style="width: 34px"
|
||||
>
|
||||
<span class="tox-icon tox-tbtn__icon-wrap"
|
||||
><svg width="24" height="24" focusable="false">
|
||||
<path
|
||||
d="M8 5h8v2h-2v12h-2V7h-2v12H8v-7c-.5 0-1 0-1.4-.3A3.4 3.4 0 0 1 4.8 10a3.3 3.3 0 0 1 0-2.8 3.4 3.4 0 0 1 1.8-1.8L8 5Zm12 11.2a1 1 0 1 1-1 1.6l-3-2a1 1 0 0 1 0-1.6l3-2a1 1 0 1 1 1 1.6L18.4 15l1.8 1.2Z"
|
||||
fill-rule="evenodd"
|
||||
></path></svg
|
||||
></span>
|
||||
</button>
|
||||
</div>
|
||||
<div role="toolbar" data-alloy-tabstop="true" class="tox-toolbar__group">
|
||||
<button
|
||||
aria-label="Reveal or hide additional toolbar items"
|
||||
data-mce-name="overflow-button"
|
||||
type="button"
|
||||
tabindex="-1"
|
||||
data-alloy-tabstop="true"
|
||||
class="tox-tbtn"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<span class="tox-icon tox-tbtn__icon-wrap"
|
||||
><svg width="24" height="24" focusable="false">
|
||||
<path
|
||||
d="M6 10a2 2 0 0 0-2 2c0 1.1.9 2 2 2a2 2 0 0 0 2-2 2 2 0 0 0-2-2Zm12 0a2 2 0 0 0-2 2c0 1.1.9 2 2 2a2 2 0 0 0 2-2 2 2 0 0 0-2-2Zm-6 0a2 2 0 0 0-2 2c0 1.1.9 2 2 2a2 2 0 0 0 2-2 2 2 0 0 0-2-2Z"
|
||||
fill-rule="nonzero"
|
||||
></path></svg
|
||||
></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
role="group"
|
||||
class="tox-toolbar__overflow tox-toolbar__overflow--closed"
|
||||
style="height: 0px"
|
||||
>
|
||||
<div
|
||||
aria-label="indentation"
|
||||
role="toolbar"
|
||||
data-alloy-tabstop="true"
|
||||
class="tox-toolbar__group"
|
||||
>
|
||||
<button
|
||||
aria-label="Decrease indent"
|
||||
data-mce-name="outdent"
|
||||
type="button"
|
||||
tabindex="-1"
|
||||
class="tox-tbtn tox-tbtn--disabled"
|
||||
aria-disabled="true"
|
||||
style="width: 34px"
|
||||
>
|
||||
<span class="tox-icon tox-tbtn__icon-wrap"
|
||||
><svg width="24" height="24" focusable="false">
|
||||
<path
|
||||
d="M7 5h12c.6 0 1 .4 1 1s-.4 1-1 1H7a1 1 0 1 1 0-2Zm5 4h7c.6 0 1 .4 1 1s-.4 1-1 1h-7a1 1 0 0 1 0-2Zm0 4h7c.6 0 1 .4 1 1s-.4 1-1 1h-7a1 1 0 0 1 0-2Zm-5 4h12a1 1 0 0 1 0 2H7a1 1 0 0 1 0-2Zm1.6-3.8a1 1 0 0 1-1.2 1.6l-3-2a1 1 0 0 1 0-1.6l3-2a1 1 0 0 1 1.2 1.6L6.8 12l1.8 1.2Z"
|
||||
fill-rule="evenodd"
|
||||
></path></svg
|
||||
></span></button
|
||||
><button
|
||||
aria-label="Increase indent"
|
||||
data-mce-name="indent"
|
||||
type="button"
|
||||
tabindex="-1"
|
||||
class="tox-tbtn"
|
||||
aria-disabled="false"
|
||||
style="width: 34px"
|
||||
>
|
||||
<span class="tox-icon tox-tbtn__icon-wrap"
|
||||
><svg width="24" height="24" focusable="false">
|
||||
<path
|
||||
d="M7 5h12c.6 0 1 .4 1 1s-.4 1-1 1H7a1 1 0 1 1 0-2Zm5 4h7c.6 0 1 .4 1 1s-.4 1-1 1h-7a1 1 0 0 1 0-2Zm0 4h7c.6 0 1 .4 1 1s-.4 1-1 1h-7a1 1 0 0 1 0-2Zm-5 4h12a1 1 0 0 1 0 2H7a1 1 0 0 1 0-2Zm-2.6-3.8L6.2 12l-1.8-1.2a1 1 0 0 1 1.2-1.6l3 2a1 1 0 0 1 0 1.6l-3 2a1 1 0 1 1-1.2-1.6Z"
|
||||
fill-rule="evenodd"
|
||||
></path></svg
|
||||
></span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
aria-label="lists"
|
||||
role="toolbar"
|
||||
data-alloy-tabstop="true"
|
||||
class="tox-toolbar__group"
|
||||
>
|
||||
<button
|
||||
aria-label="Bullet list"
|
||||
data-mce-name="bullist"
|
||||
type="button"
|
||||
tabindex="-1"
|
||||
class="tox-tbtn"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
style="width: 34px"
|
||||
>
|
||||
<span class="tox-icon tox-tbtn__icon-wrap"
|
||||
><svg width="24" height="24" focusable="false">
|
||||
<path
|
||||
d="M11 5h8c.6 0 1 .4 1 1s-.4 1-1 1h-8a1 1 0 0 1 0-2Zm0 6h8c.6 0 1 .4 1 1s-.4 1-1 1h-8a1 1 0 0 1 0-2Zm0 6h8c.6 0 1 .4 1 1s-.4 1-1 1h-8a1 1 0 0 1 0-2ZM4.5 6c0-.4.1-.8.4-1 .3-.4.7-.5 1.1-.5.4 0 .8.1 1 .4.4.3.5.7.5 1.1 0 .4-.1.8-.4 1-.3.4-.7.5-1.1.5-.4 0-.8-.1-1-.4-.4-.3-.5-.7-.5-1.1Zm0 6c0-.4.1-.8.4-1 .3-.4.7-.5 1.1-.5.4 0 .8.1 1 .4.4.3.5.7.5 1.1 0 .4-.1.8-.4 1-.3.4-.7.5-1.1.5-.4 0-.8-.1-1-.4-.4-.3-.5-.7-.5-1.1Zm0 6c0-.4.1-.8.4-1 .3-.4.7-.5 1.1-.5.4 0 .8.1 1 .4.4.3.5.7.5 1.1 0 .4-.1.8-.4 1-.3.4-.7.5-1.1.5-.4 0-.8-.1-1-.4-.4-.3-.5-.7-.5-1.1Z"
|
||||
fill-rule="evenodd"
|
||||
></path></svg
|
||||
></span></button
|
||||
><button
|
||||
aria-label="Numbered list"
|
||||
data-mce-name="numlist"
|
||||
type="button"
|
||||
tabindex="-1"
|
||||
class="tox-tbtn"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
style="width: 34px"
|
||||
>
|
||||
<span class="tox-icon tox-tbtn__icon-wrap"
|
||||
><svg width="24" height="24" focusable="false">
|
||||
<path
|
||||
d="M10 17h8c.6 0 1 .4 1 1s-.4 1-1 1h-8a1 1 0 0 1 0-2Zm0-6h8c.6 0 1 .4 1 1s-.4 1-1 1h-8a1 1 0 0 1 0-2Zm0-6h8c.6 0 1 .4 1 1s-.4 1-1 1h-8a1 1 0 1 1 0-2ZM6 4v3.5c0 .3-.2.5-.5.5a.5.5 0 0 1-.5-.5V5h-.5a.5.5 0 0 1 0-1H6Zm-1 8.8.2.2h1.3c.3 0 .5.2.5.5s-.2.5-.5.5H4.9a1 1 0 0 1-.9-1V13c0-.4.3-.8.6-1l1.2-.4.2-.3a.2.2 0 0 0-.2-.2H4.5a.5.5 0 0 1-.5-.5c0-.3.2-.5.5-.5h1.6c.5 0 .9.4.9 1v.1c0 .4-.3.8-.6 1l-1.2.4-.2.3ZM7 17v2c0 .6-.4 1-1 1H4.5a.5.5 0 0 1 0-1h1.2c.2 0 .3-.1.3-.3 0-.2-.1-.3-.3-.3H4.4a.4.4 0 1 1 0-.8h1.3c.2 0 .3-.1.3-.3 0-.2-.1-.3-.3-.3H4.5a.5.5 0 1 1 0-1H6c.6 0 1 .4 1 1Z"
|
||||
fill-rule="evenodd"
|
||||
></path></svg
|
||||
></span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
aria-label="advanced"
|
||||
role="toolbar"
|
||||
data-alloy-tabstop="true"
|
||||
class="tox-toolbar__group"
|
||||
>
|
||||
<button
|
||||
aria-label="Equation editor"
|
||||
data-mce-name="tiny_equation"
|
||||
type="button"
|
||||
tabindex="-1"
|
||||
class="tox-tbtn"
|
||||
aria-disabled="false"
|
||||
aria-pressed="false"
|
||||
style="width: 34px"
|
||||
>
|
||||
<span class="tox-icon tox-tbtn__icon-wrap"
|
||||
><svg
|
||||
data-buttonsource="moodle"
|
||||
width="24"
|
||||
height="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
focusable="false"
|
||||
>
|
||||
<image
|
||||
href="https://school.moodledemo.net/theme/image.php/boost/tiny_equation/1775905339/icon"
|
||||
width="24"
|
||||
height="24"
|
||||
></image></svg
|
||||
></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tox-anchorbar"></div>
|
||||
</div>
|
||||
<div class="tox-sidebar-wrap" style="height: 225px">
|
||||
<div class="tox-edit-area">
|
||||
<iframe
|
||||
id="q125:6_answer_id_ifr"
|
||||
frameborder="0"
|
||||
allowtransparency="true"
|
||||
title="Rich text area"
|
||||
class="tox-edit-area__iframe"
|
||||
srcdoc='<!DOCTYPE html><html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /></head><body id="tinymce" class="mce-content-body " data-id="q125:6_answer_id" aria-label="Rich text area. Press ALT-0 for help."><br></body></html>'
|
||||
></iframe>
|
||||
</div>
|
||||
<div role="presentation" class="tox-sidebar">
|
||||
<div
|
||||
data-alloy-tabstop="true"
|
||||
tabindex="-1"
|
||||
class="tox-sidebar__slider tox-sidebar--sliding-closed"
|
||||
style="width: 0px"
|
||||
>
|
||||
<div class="tox-sidebar__pane-container"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tox-bottom-anchorbar"></div>
|
||||
</div>
|
||||
<div aria-hidden="true" class="tox-view-wrap" style="display: none">
|
||||
<div class="tox-view-wrap__slot-container"></div>
|
||||
</div>
|
||||
<div class="tox-statusbar">
|
||||
<div
|
||||
class="tox-statusbar__text-container tox-statusbar__text-container--flex-start"
|
||||
>
|
||||
<div
|
||||
role="navigation"
|
||||
data-alloy-tabstop="true"
|
||||
class="tox-statusbar__path"
|
||||
aria-disabled="false"
|
||||
>
|
||||
<div
|
||||
data-index="0"
|
||||
role="button"
|
||||
tabindex="-1"
|
||||
class="tox-statusbar__path-item"
|
||||
aria-disabled="false"
|
||||
>
|
||||
p
|
||||
</div>
|
||||
</div>
|
||||
<div class="tox-statusbar__right-container">
|
||||
<button
|
||||
type="button"
|
||||
tabindex="-1"
|
||||
data-alloy-tabstop="true"
|
||||
class="tox-statusbar__wordcount"
|
||||
>
|
||||
0 words</button
|
||||
><span class="tox-statusbar__branding"
|
||||
><a
|
||||
href="https://www.tiny.cloud/powered-by-tiny?utm_campaign=poweredby&utm_source=tiny&utm_medium=referral&utm_content=v7"
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
aria-label="Build with TinyMCE"
|
||||
tabindex="-1"
|
||||
>Build with
|
||||
<svg
|
||||
height="16"
|
||||
viewBox="0 0 80 16"
|
||||
width="80"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g opacity=".8">
|
||||
<path
|
||||
d="m80 3.537v-2.202h-7.976v11.585h7.976v-2.25h-5.474v-2.621h4.812v-2.069h-4.812v-2.443zm-10.647 6.929c-.493.217-1.13.337-1.864.337s-1.276-.156-1.805-.47a3.732 3.732 0 0 1 -1.3-1.298c-.324-.554-.48-1.191-.48-1.877s.156-1.335.48-1.877a3.635 3.635 0 0 1 1.3-1.299 3.466 3.466 0 0 1 1.805-.481c.65 0 .914.06 1.263.18.36.12.698.277.986.47.289.192.578.384.842.6l.12.085v-2.586l-.023-.024c-.385-.35-.855-.614-1.384-.818-.53-.205-1.155-.313-1.877-.313-.721 0-1.6.144-2.333.445a5.773 5.773 0 0 0 -1.937 1.251 5.929 5.929 0 0 0 -1.324 1.9c-.324.735-.48 1.565-.48 2.455s.156 1.72.48 2.454c.325.734.758 1.383 1.324 1.913.553.53 1.215.938 1.937 1.25a6.286 6.286 0 0 0 2.333.434c.819 0 1.384-.108 1.961-.313.59-.216 1.083-.505 1.468-.866l.024-.024v-2.49l-.12.096c-.41.337-.878.626-1.396.866zm-14.869-4.15-4.8-5.04-.024-.025h-.902v11.67h2.502v-6.847l2.827 3.08.385.409.397-.41 2.791-3.067v6.845h2.502v-11.679h-.902l-4.788 5.052z"
|
||||
></path>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="m15.543 5.137c0-3.032-2.466-5.113-4.957-5.137-.36 0-.745.024-1.094.096-.157.024-3.85.758-3.85.758-3.032.602-4.62 2.466-4.704 4.788-.024.89-.024 4.27-.024 4.27.036 3.165 2.406 5.138 5.017 5.126.337 0 1.119-.109 1.287-.145.144-.024.385-.084.746-.144.661-.12 1.684-.325 3.067-.602 2.37-.409 4.103-2.009 4.44-4.33.156-1.023.084-4.692.084-4.692zm-3.213 3.308-2.346.457v2.31l-5.859 1.143v-5.75l2.346-.458v3.441l3.513-.686v-3.44l-3.513.685v-2.297l5.859-1.143v5.75zm20.09-3.296-.083-1.023h-2.13v8.794h2.346v-4.884c0-1.107.95-1.985 2.057-1.997 1.095 0 1.901.89 1.901 1.997v4.884h2.346v-5.245c-.012-2.105-1.588-3.777-3.67-3.765a3.764 3.764 0 0 0 -2.778 1.25l.012-.011zm-6.014-4.102 2.346-.458v2.298l-2.346.457z"
|
||||
fill-rule="evenodd"
|
||||
></path>
|
||||
<path d="m28.752 4.126h-2.346v8.794h2.346z"></path>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="m43.777 15.483 4.043-11.357h-2.418l-1.54 4.355-.445 1.324-.36-1.324-1.54-4.355h-2.418l3.151 8.794-1.083 3.08zm-21.028-5.51c0 .722.541 1.034.878 1.034s.638-.048.95-.144l.518 1.708c-.217.145-.879.518-2.13.518a2.565 2.565 0 0 1 -2.562-2.587c-.024-1.082-.024-2.49 0-4.21h-1.54v-2.142h1.54v-1.912l2.346-.458v2.37h2.201v2.142h-2.2v3.693-.012z"
|
||||
fill-rule="evenodd"
|
||||
></path>
|
||||
</g></svg></a
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Press the Up and Down arrow keys to resize the editor."
|
||||
data-mce-name="resize-handle"
|
||||
data-alloy-tabstop="true"
|
||||
tabindex="-1"
|
||||
class="tox-statusbar__resize-handle"
|
||||
>
|
||||
<svg width="10" height="10" focusable="false">
|
||||
<g fill-rule="nonzero">
|
||||
<path
|
||||
d="M8.1 1.1A.5.5 0 1 1 9 2l-7 7A.5.5 0 1 1 1 8l7-7ZM8.1 5.1A.5.5 0 1 1 9 6l-3 3A.5.5 0 1 1 5 8l3-3Z"
|
||||
></path>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div aria-hidden="true" class="tox-throbber" style="display: none"></div>
|
||||
</div>
|
||||
<div
|
||||
class="tox tox-silver-sink tox-silver-popup-sink tox-tinymce-aux"
|
||||
style="position: relative"
|
||||
></div>
|
||||
</div>
|
||||
<div><input type="hidden" name="q125:6_answerformat" value="1" /></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="attachments"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,121 @@
|
||||
<div id="question-125-2" class="que multichoice deferredfeedback notyetanswered">
|
||||
<div class="info">
|
||||
<h3 class="no">Question <span class="qno">2</span></h3>
|
||||
<div class="state">Not yet answered</div>
|
||||
<div class="grade">Marked out of 1.00</div>
|
||||
<div class="questionflag editable" id="yui_3_18_1_1_1775909302600_24">
|
||||
<input type="hidden" name="q125:2_:flagged" value="0" /><input
|
||||
type="hidden"
|
||||
value="qaid=799&qubaid=125&qid=3700&slot=2&checksum=c534dfa95146a052b3bdd2d1dd744a46&sesskey=MRh4T7j2fU&newstate="
|
||||
class="questionflagpostdata"
|
||||
/>
|
||||
<input
|
||||
type="hidden"
|
||||
class="questionflagvalue"
|
||||
id="q125:2_:flaggedcheckbox"
|
||||
name="q125:2_:flagged"
|
||||
value="0"
|
||||
/><a
|
||||
tabindex="0"
|
||||
class="aabtn"
|
||||
role="button"
|
||||
aria-pressed="false"
|
||||
aria-label="Flagged"
|
||||
title="Flag this question for future reference"
|
||||
><img
|
||||
class="questionflagimage"
|
||||
src="https://school.moodledemo.net/theme/image.php/boost/core/1775905339/i/unflagged"
|
||||
alt=""
|
||||
/>Flag question</a
|
||||
>
|
||||
</div>
|
||||
<div class="editquestion">
|
||||
<a
|
||||
href="https://school.moodledemo.net/question/bank/editquestion/question.php?cmid=1655&returnurl=%2Fmod%2Fquiz%2Fattempt.php%3Fattempt%3D89%26cmid%3D1655%23question-125-2&id=3700"
|
||||
><i class="icon fa fa-pen fa-fw iconsmall" title="Edit" role="img" aria-label="Edit"></i
|
||||
>Edit question</a
|
||||
>
|
||||
</div>
|
||||
<span class="badge bg-primary text-light">v2 (latest)</span>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="formulation clearfix">
|
||||
<h4 class="accesshide">Question text</h4>
|
||||
<input type="hidden" name="q125:2_:sequencecheck" value="1" />
|
||||
<div class="qtext" style="cursor: pointer">
|
||||
<div class="clearfix">
|
||||
<p>Which of the following actions can help you develop resilience?</p>
|
||||
</div>
|
||||
</div>
|
||||
<fieldset class="ablock no-overflow visual-scroll-x">
|
||||
<legend class="prompt h6 fw-normal">
|
||||
<span class="visually-hidden">Question 2</span> Select one or more:
|
||||
</legend>
|
||||
<div class="answer">
|
||||
<div class="r0">
|
||||
<input type="hidden" name="q125:2_choice0" value="0" /><input
|
||||
type="checkbox"
|
||||
name="q125:2_choice0"
|
||||
value="1"
|
||||
id="q125:2_choice0"
|
||||
aria-labelledby="q125:2_choice0_label"
|
||||
/>
|
||||
<div class="d-flex w-auto" id="q125:2_choice0_label" data-region="answer-label">
|
||||
<div class="flex-fill ms-1">
|
||||
<p> Ignoring emotions and pushing through hardships without reflection.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="r1">
|
||||
<input type="hidden" name="q125:2_choice1" value="0" /><input
|
||||
type="checkbox"
|
||||
name="q125:2_choice1"
|
||||
value="1"
|
||||
id="q125:2_choice1"
|
||||
aria-labelledby="q125:2_choice1_label"
|
||||
/>
|
||||
<div class="d-flex w-auto" id="q125:2_choice1_label" data-region="answer-label">
|
||||
<div class="flex-fill ms-1">
|
||||
<p>
|
||||
Practising self-care, such as maintaining a healthy sleep routine and regular
|
||||
exercise
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="r0">
|
||||
<input type="hidden" name="q125:2_choice2" value="0" /><input
|
||||
type="checkbox"
|
||||
name="q125:2_choice2"
|
||||
value="1"
|
||||
id="q125:2_choice2"
|
||||
aria-labelledby="q125:2_choice2_label"
|
||||
/>
|
||||
<div class="d-flex w-auto" id="q125:2_choice2_label" data-region="answer-label">
|
||||
<div class="flex-fill ms-1">
|
||||
<p>
|
||||
Seeking support from friends, mentors, or professional resources when facing
|
||||
difficulties.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="r1">
|
||||
<input type="hidden" name="q125:2_choice3" value="0" /><input
|
||||
type="checkbox"
|
||||
name="q125:2_choice3"
|
||||
value="1"
|
||||
id="q125:2_choice3"
|
||||
aria-labelledby="q125:2_choice3_label"
|
||||
/>
|
||||
<div class="d-flex w-auto" id="q125:2_choice3_label" data-region="answer-label">
|
||||
<div class="flex-fill ms-1">
|
||||
<p>Avoiding challenges and stressful situations to prevent failure</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,63 @@
|
||||
<div id="question-125-5" class="que numerical deferredfeedback notyetanswered">
|
||||
<div class="info">
|
||||
<h3 class="no">Question <span class="qno">5</span></h3>
|
||||
<div class="state">Not yet answered</div>
|
||||
<div class="grade">Marked out of 1.00</div>
|
||||
<div class="questionflag editable" id="yui_3_18_1_1_1775909302600_42">
|
||||
<input type="hidden" name="q125:5_:flagged" value="0" /><input
|
||||
type="hidden"
|
||||
value="qaid=802&qubaid=125&qid=3702&slot=5&checksum=2c2029ed58598b4af63b0db74bebcaab&sesskey=MRh4T7j2fU&newstate="
|
||||
class="questionflagpostdata"
|
||||
/>
|
||||
<input
|
||||
type="hidden"
|
||||
class="questionflagvalue"
|
||||
id="q125:5_:flaggedcheckbox"
|
||||
name="q125:5_:flagged"
|
||||
value="0"
|
||||
/><a
|
||||
tabindex="0"
|
||||
class="aabtn"
|
||||
role="button"
|
||||
aria-pressed="false"
|
||||
aria-label="Flagged"
|
||||
title="Flag this question for future reference"
|
||||
><img
|
||||
class="questionflagimage"
|
||||
src="https://school.moodledemo.net/theme/image.php/boost/core/1775905339/i/unflagged"
|
||||
alt=""
|
||||
/>Flag question</a
|
||||
>
|
||||
</div>
|
||||
<div class="editquestion">
|
||||
<a
|
||||
href="https://school.moodledemo.net/question/bank/editquestion/question.php?cmid=1655&returnurl=%2Fmod%2Fquiz%2Fattempt.php%3Fattempt%3D89%26cmid%3D1655%23question-125-5&id=3702"
|
||||
><i class="icon fa fa-pen fa-fw iconsmall" title="Edit" role="img" aria-label="Edit"></i
|
||||
>Edit question</a
|
||||
>
|
||||
</div>
|
||||
<span class="badge bg-primary text-light">v1 (latest)</span>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="formulation clearfix">
|
||||
<h4 class="accesshide">Question text</h4>
|
||||
<input type="hidden" name="q125:5_:sequencecheck" value="1" />
|
||||
<div class="qtext" style="cursor: pointer">
|
||||
<div class="clearfix">
|
||||
<p>What is 2 + 2?</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ablock d-flex flex-wrap align-items-center">
|
||||
<label for="q125:5_answer">Answer: <span class="visually-hidden">Question 5</span></label
|
||||
><span class="answer"
|
||||
><input
|
||||
type="text"
|
||||
name="q125:5_answer"
|
||||
id="q125:5_answer"
|
||||
size="30"
|
||||
class="form-control d-inline"
|
||||
/></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,52 @@
|
||||
From moodle docs: https://docs.moodle.org/501/en/Question_types
|
||||
|
||||
Standard question types
|
||||
Calculated
|
||||
Calculated questions offer a way to create individual numerical questions by the use of wildcards that are substituted with individual values when the quiz is taken. More on the Calculated question type
|
||||
|
||||
Calculated multi-choice
|
||||
Calculated multichoice questions are like multichoice questions with the additional property that the elements to select can include formula results from numeric values that are selected randomly from a set when the quiz is taken. They use the same wildcards than Calculated questions and their wildcards can be shared with other Calculated multichoice or regular Calculated questions.
|
||||
|
||||
The main difference is that the formula is included in the answer choice as {=...} i.e. if you calculate the surface of a rectangle {={l}*{w}}.
|
||||
|
||||
More on the Calculated Multi-Choice question type.
|
||||
|
||||
Calculated simple
|
||||
Simple calculated questions offer a way to create individual numerical questions whose response is the result of a numerical formula which contain variable numerical values by the use of wildcards (i.e. {x} , {y}) that are substituted with random values when the quiz is taken.
|
||||
|
||||
The simple calculated questions offers the most used features of the calculated question with a much simpler creation interface. More on the Simple Calculated question type.
|
||||
|
||||
Drag and drop into text
|
||||
Students select missing words or phrases and add them to text by dragging boxes to the correct location. Items may be grouped and used more than once. More on the Drag and drop into text question type.
|
||||
|
||||
Essay
|
||||
This allows students to write at length on a particular subject and must be manually graded.
|
||||
|
||||
It is possible for a teacher to create a template to scaffold the student's answer in order to give them extra support. The template is then reproduced in the text editor when the student starts to answer the question. See YouTube video Essay scaffold with the Moodle quiz It is also possible to include grading information for teachers marking the essay to refer to as they assess the essays,
|
||||
|
||||
Matching
|
||||
A list of sub-questions is provided, along with a list of answers. The respondent must "match" the correct answers with each question. More on the Matching question type
|
||||
|
||||
Embedded Answers (Cloze Test / Gap Fill)
|
||||
These very flexible questions consist of a passage of text (in Moodle format) that has various answers embedded within it, including multiple choice, short answers and numerical answers. More on the Embedded Answers question type
|
||||
|
||||
Multiple choice
|
||||
With the Multiple Choice question type you can create single-answer and multiple-answer questions, include pictures, sound or other media in the question and/or answer options (by inserting HTML) and weight individual answers.
|
||||
|
||||
Ordering
|
||||
The ordering question type displays several items (words, phrases or images) in a random order which are to be dragged into the correct sequential order. See Ordering question type for more information.
|
||||
|
||||
Short Answer
|
||||
In response to a question (that may include an image), the respondent types a word or phrase. There may several possible correct answers, with different grades. Answers may or may not be sensitive to case. More on the Short Answer question type
|
||||
|
||||
Numerical
|
||||
From the student perspective, a numerical question looks just like a short-answer question. The difference is that numerical answers are allowed to have an accepted error. This allows a continuous range of answers to be set. More on the Numerical question type
|
||||
|
||||
Random short-answer matching
|
||||
From the student perspective, this looks just like a Matching question. The difference is that the sub-questions are drawn randomly from Short Answer questions in the current category. More on the Random Short-Answer Matching question type
|
||||
|
||||
Select missing words
|
||||
Students select a missing word or phrase from a dropdown menu. Items may be grouped and used more than once. More on the Select missing words question type
|
||||
|
||||
True/False
|
||||
In response to a question (that may include an image), the respondent selects from two options: True or False. More on the True/False question type
|
||||
@@ -0,0 +1,64 @@
|
||||
<div id="question-125-4" class="que shortanswer deferredfeedback notyetanswered">
|
||||
<div class="info">
|
||||
<h3 class="no">Question <span class="qno">4</span></h3>
|
||||
<div class="state">Not yet answered</div>
|
||||
<div class="grade">Marked out of 1.00</div>
|
||||
<div class="questionflag editable" id="yui_3_18_1_1_1775909302600_36">
|
||||
<input type="hidden" name="q125:4_:flagged" value="0" /><input
|
||||
type="hidden"
|
||||
value="qaid=801&qubaid=125&qid=3701&slot=4&checksum=e963ee069c13bab0bb84c37d94edf69b&sesskey=MRh4T7j2fU&newstate="
|
||||
class="questionflagpostdata"
|
||||
/>
|
||||
<input
|
||||
type="hidden"
|
||||
class="questionflagvalue"
|
||||
id="q125:4_:flaggedcheckbox"
|
||||
name="q125:4_:flagged"
|
||||
value="0"
|
||||
/><a
|
||||
tabindex="0"
|
||||
class="aabtn"
|
||||
role="button"
|
||||
aria-pressed="false"
|
||||
aria-label="Flagged"
|
||||
title="Flag this question for future reference"
|
||||
><img
|
||||
class="questionflagimage"
|
||||
src="https://school.moodledemo.net/theme/image.php/boost/core/1775905339/i/unflagged"
|
||||
alt=""
|
||||
/>Flag question</a
|
||||
>
|
||||
</div>
|
||||
<div class="editquestion">
|
||||
<a
|
||||
href="https://school.moodledemo.net/question/bank/editquestion/question.php?cmid=1655&returnurl=%2Fmod%2Fquiz%2Fattempt.php%3Fattempt%3D89%26cmid%3D1655%23question-125-4&id=3701"
|
||||
><i class="icon fa fa-pen fa-fw iconsmall" title="Edit" role="img" aria-label="Edit"></i
|
||||
>Edit question</a
|
||||
>
|
||||
</div>
|
||||
<span class="badge bg-primary text-light">v1 (latest)</span>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="formulation clearfix">
|
||||
<h4 class="accesshide">Question text</h4>
|
||||
<input type="hidden" name="q125:4_:sequencecheck" value="1" />
|
||||
<div class="qtext" style="cursor: pointer">
|
||||
<div class="clearfix">
|
||||
<p>How is a post on Twitter called?</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ablock d-flex flex-wrap align-items-center">
|
||||
<label for="q125:4_answer"
|
||||
>Answer: <span class="visually-hidden">Question 4</span
|
||||
><span class="answer"
|
||||
><input
|
||||
type="text"
|
||||
name="q125:4_answer"
|
||||
id="q125:4_answer"
|
||||
size="80"
|
||||
class="form-control d-inline" /></span
|
||||
></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,120 @@
|
||||
<div id="question-125-1" class="que multichoice deferredfeedback notyetanswered">
|
||||
<div class="info">
|
||||
<h3 class="no">Question <span class="qno">1</span></h3>
|
||||
<div class="state">Not yet answered</div>
|
||||
<div class="grade">Marked out of 1.00</div>
|
||||
<div class="questionflag editable" id="yui_3_18_1_1_1775909302600_18">
|
||||
<input type="hidden" name="q125:1_:flagged" value="0" /><input
|
||||
type="hidden"
|
||||
value="qaid=798&qubaid=125&qid=3699&slot=1&checksum=e0f716344fc5402f32878bcbf0aa8342&sesskey=MRh4T7j2fU&newstate="
|
||||
class="questionflagpostdata"
|
||||
/>
|
||||
<input
|
||||
type="hidden"
|
||||
class="questionflagvalue"
|
||||
id="q125:1_:flaggedcheckbox"
|
||||
name="q125:1_:flagged"
|
||||
value="0"
|
||||
/><a
|
||||
tabindex="0"
|
||||
class="aabtn"
|
||||
role="button"
|
||||
aria-pressed="false"
|
||||
aria-label="Flagged"
|
||||
title="Flag this question for future reference"
|
||||
><img
|
||||
class="questionflagimage"
|
||||
src="https://school.moodledemo.net/theme/image.php/boost/core/1775905339/i/unflagged"
|
||||
alt=""
|
||||
/>Flag question</a
|
||||
>
|
||||
</div>
|
||||
<div class="editquestion">
|
||||
<a
|
||||
href="https://school.moodledemo.net/question/bank/editquestion/question.php?cmid=1655&returnurl=%2Fmod%2Fquiz%2Fattempt.php%3Fattempt%3D89%26cmid%3D1655%23&id=3699"
|
||||
><i class="icon fa fa-pen fa-fw iconsmall" title="Edit" role="img" aria-label="Edit"></i
|
||||
>Edit question</a
|
||||
>
|
||||
</div>
|
||||
<span class="badge bg-primary text-light">v2 (latest)</span>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="formulation clearfix">
|
||||
<h4 class="accesshide">Question text</h4>
|
||||
<input type="hidden" name="q125:1_:sequencecheck" value="1" />
|
||||
<div class="qtext" style="cursor: pointer">
|
||||
<div class="clearfix">
|
||||
<p>What is resilience?</p>
|
||||
</div>
|
||||
</div>
|
||||
<fieldset class="ablock no-overflow visual-scroll-x">
|
||||
<legend class="prompt h6 fw-normal visually-hidden">
|
||||
<span class="visually-hidden">Question 1</span> Answer
|
||||
</legend>
|
||||
<div class="answer">
|
||||
<div class="r0">
|
||||
<input
|
||||
type="radio"
|
||||
name="q125:1_answer"
|
||||
value="0"
|
||||
id="q125:1_answer0"
|
||||
aria-labelledby="q125:1_answer0_label"
|
||||
/>
|
||||
<div class="d-flex w-auto" id="q125:1_answer0_label" data-region="answer-label">
|
||||
<div class="flex-fill ms-1">
|
||||
<p>The ability to recover from difficulties and adapt to challenging situations</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="r1">
|
||||
<input
|
||||
type="radio"
|
||||
name="q125:1_answer"
|
||||
value="1"
|
||||
id="q125:1_answer1"
|
||||
aria-labelledby="q125:1_answer1_label"
|
||||
/>
|
||||
<div class="d-flex w-auto" id="q125:1_answer1_label" data-region="answer-label">
|
||||
<div class="flex-fill ms-1">
|
||||
<p>The skill of controlling other people’s emotions during a crisis</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="r0">
|
||||
<input
|
||||
type="radio"
|
||||
name="q125:1_answer"
|
||||
value="2"
|
||||
id="q125:1_answer2"
|
||||
aria-labelledby="q125:1_answer2_label"
|
||||
/>
|
||||
<div class="d-flex w-auto" id="q125:1_answer2_label" data-region="answer-label">
|
||||
<div class="flex-fill ms-1">
|
||||
<p>The ability to avoid stressful situations entirely.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
id="q125:1_clearchoice"
|
||||
class="qtype_multichoice_clearchoice visually-hidden"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="q125:1_answer"
|
||||
id="q125:1_answer-1"
|
||||
value="-1"
|
||||
class="visually-hidden"
|
||||
aria-hidden="true"
|
||||
checked="checked"
|
||||
/><label for="q125:1_answer-1"
|
||||
><a tabindex="-1" role="button" class="btn btn-link ms-3 mt-n1" href="#"
|
||||
>Clear my choice</a
|
||||
></label
|
||||
>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,73 @@
|
||||
<div id="question-125-3" class="que truefalse deferredfeedback notyetanswered">
|
||||
<div class="info">
|
||||
<h3 class="no">Question <span class="qno">3</span></h3>
|
||||
<div class="state">Not yet answered</div>
|
||||
<div class="grade">Marked out of 1.00</div>
|
||||
<div class="questionflag editable" id="yui_3_18_1_1_1775909302600_30">
|
||||
<input type="hidden" name="q125:3_:flagged" value="0" /><input
|
||||
type="hidden"
|
||||
value="qaid=800&qubaid=125&qid=3698&slot=3&checksum=57228a123cfb296aa6bd9357997dfbed&sesskey=MRh4T7j2fU&newstate="
|
||||
class="questionflagpostdata"
|
||||
/>
|
||||
<input
|
||||
type="hidden"
|
||||
class="questionflagvalue"
|
||||
id="q125:3_:flaggedcheckbox"
|
||||
name="q125:3_:flagged"
|
||||
value="0"
|
||||
/><a
|
||||
tabindex="0"
|
||||
class="aabtn"
|
||||
role="button"
|
||||
aria-pressed="false"
|
||||
aria-label="Flagged"
|
||||
title="Flag this question for future reference"
|
||||
><img
|
||||
class="questionflagimage"
|
||||
src="https://school.moodledemo.net/theme/image.php/boost/core/1775905339/i/unflagged"
|
||||
alt=""
|
||||
/>Flag question</a
|
||||
>
|
||||
</div>
|
||||
<div class="editquestion">
|
||||
<a
|
||||
href="https://school.moodledemo.net/question/bank/editquestion/question.php?cmid=1655&returnurl=%2Fmod%2Fquiz%2Fattempt.php%3Fattempt%3D89%26cmid%3D1655%23question-125-3&id=3698"
|
||||
><i class="icon fa fa-pen fa-fw iconsmall" title="Edit" role="img" aria-label="Edit"></i
|
||||
>Edit question</a
|
||||
>
|
||||
</div>
|
||||
<span class="badge bg-primary text-light">v1 (latest)</span>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="formulation clearfix">
|
||||
<h4 class="accesshide">Question text</h4>
|
||||
<input type="hidden" name="q125:3_:sequencecheck" value="1" />
|
||||
<div class="qtext" style="cursor: pointer">
|
||||
<div class="clearfix">
|
||||
<p>Is resilience important?</p>
|
||||
</div>
|
||||
</div>
|
||||
<fieldset class="ablock">
|
||||
<legend class="prompt h6 fw-normal visually-hidden">
|
||||
<span class="visually-hidden">Question 3</span> Answer
|
||||
</legend>
|
||||
<div class="answer">
|
||||
<div class="r0">
|
||||
<input type="radio" name="q125:3_answer" value="1" id="q125:3_answertrue" /><label
|
||||
for="q125:3_answertrue"
|
||||
class="ms-1"
|
||||
>True</label
|
||||
>
|
||||
</div>
|
||||
<div class="r1">
|
||||
<input type="radio" name="q125:3_answer" value="0" id="q125:3_answerfalse" /><label
|
||||
for="q125:3_answerfalse"
|
||||
class="ms-1"
|
||||
>False</label
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2,6 +2,8 @@ import type Config from '../types/config';
|
||||
import imageToBase64 from 'background/utils/image-to-base64';
|
||||
import isGPTModelGreaterOrEqualTo4 from 'background/utils/version-support-images';
|
||||
import { ChatCompletionMessageParam, ChatCompletionUserMessageParam } from 'openai/resources';
|
||||
import { parseMoodleQuestion } from './parse-question';
|
||||
import { MoodleQuestionQuery /* MoodleQuestionType */ } from '../types/question-types';
|
||||
|
||||
// The attempt and the cmid allow us to identify a quiz
|
||||
type History = {
|
||||
@@ -11,18 +13,13 @@ type History = {
|
||||
history: ChatCompletionMessageParam[];
|
||||
};
|
||||
|
||||
const INSTRUCTION: string = `
|
||||
Act as a quiz solver for the best notation with the following rules:
|
||||
- If no answer(s) are given, answer the statement as usual without following the other rules, providing the most detailed, complete and precise explanation.
|
||||
- But for the calculation provide this format 'result: <result of the equation>'
|
||||
- For 'put in order' questions, maintain the answer in the order as presented in the question but assocy the correct order to it by usin this format '<order>:<answer 1>\n<order>:<answer 2>', ignore other rules.
|
||||
- Always reply in the format: '<answer 1>\n<answer 2>\n...'.
|
||||
- Retain only the correct answer(s).
|
||||
- Maintain the same order for the answers as in the text.
|
||||
- Retain all text from the answer with its description, content or definition.
|
||||
- Only provide answers that exactly match the given answer in the text.
|
||||
- The question always has the correct answer(s), so you should always provide an answer.
|
||||
- Always respond in the same language as the user's question.
|
||||
const INSTRUCTION = `
|
||||
You are an expert quiz solver.
|
||||
Please solve the provided question based on its type and provide the correct result.
|
||||
- For choice questions, output the exact index(es) of the correct answer(s).
|
||||
- For text/numerical questions, provide the exact wording or number.
|
||||
- For essay questions, provide a highly detailed and complete response.
|
||||
Always output strict JSON according to the requested schema block.
|
||||
`.trim();
|
||||
|
||||
const SYSTEM_INSTRUCTION_MESSAGE = {
|
||||
@@ -37,7 +34,8 @@ const SYSTEM_INSTRUCTION_MESSAGE = {
|
||||
async function getContent(
|
||||
config: Config,
|
||||
questionElement: HTMLElement,
|
||||
question: string
|
||||
// We provide the structured JSON if parsed, otherwise fallback to normalized text string
|
||||
textContent: string
|
||||
): Promise<ChatCompletionUserMessageParam['content']> {
|
||||
const imagesElements = questionElement.querySelectorAll('img');
|
||||
|
||||
@@ -46,7 +44,7 @@ async function getContent(
|
||||
!isGPTModelGreaterOrEqualTo4(config.model) ||
|
||||
imagesElements.length === 0
|
||||
) {
|
||||
return question;
|
||||
return textContent;
|
||||
}
|
||||
|
||||
const contentWithImages: ChatCompletionUserMessageParam['content'] = [];
|
||||
@@ -67,7 +65,7 @@ async function getContent(
|
||||
|
||||
contentWithImages.push({
|
||||
type: 'text',
|
||||
text: question
|
||||
text: textContent
|
||||
});
|
||||
|
||||
return contentWithImages;
|
||||
@@ -126,34 +124,54 @@ async function getContentWithHistory(
|
||||
): Promise<{
|
||||
messages: [typeof SYSTEM_INSTRUCTION_MESSAGE, ...ChatCompletionMessageParam[]];
|
||||
saveResponse?: (response: string) => void;
|
||||
query: MoodleQuestionQuery | null;
|
||||
}> {
|
||||
const content = await getContent(config, questionElement, question);
|
||||
const parsedQuery = parseMoodleQuestion(questionElement, question);
|
||||
const textContent = parsedQuery ? JSON.stringify(parsedQuery, null, 2) : question;
|
||||
|
||||
const content = await getContent(config, questionElement, textContent);
|
||||
const message: ChatCompletionMessageParam = { role: 'user', content };
|
||||
|
||||
if (!config.history) return { messages: [SYSTEM_INSTRUCTION_MESSAGE, message] };
|
||||
const buildResult = (historyMsg: ChatCompletionMessageParam[]) => {
|
||||
const historyObj = { history: historyMsg };
|
||||
return {
|
||||
messages: [SYSTEM_INSTRUCTION_MESSAGE, ...historyMsg, message] as [
|
||||
typeof SYSTEM_INSTRUCTION_MESSAGE,
|
||||
...ChatCompletionMessageParam[]
|
||||
],
|
||||
query: parsedQuery,
|
||||
saveResponse(response: string) {
|
||||
if (config.history) {
|
||||
historyObj.history.push(message);
|
||||
historyObj.history.push({ role: 'assistant', content: response });
|
||||
// Note we probably need the full 'history' object here to stringify it:
|
||||
// We will recreate it or reuse the loaded one
|
||||
let historyToSave: History;
|
||||
const pastHistory: History | null = loadPastHistory();
|
||||
const newHistory: History = createNewHistory();
|
||||
if (pastHistory === null || !areHistoryFromSameQuiz(pastHistory, newHistory)) {
|
||||
historyToSave = newHistory;
|
||||
} else {
|
||||
historyToSave = pastHistory;
|
||||
}
|
||||
historyToSave.history = historyObj.history;
|
||||
sessionStorage.moodleGPTHistory = JSON.stringify(historyToSave);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
let history: History;
|
||||
if (!config.history) {
|
||||
return buildResult([]);
|
||||
}
|
||||
|
||||
const pastHistory: History | null = loadPastHistory();
|
||||
const newHistory: History = createNewHistory();
|
||||
|
||||
if (pastHistory === null || !areHistoryFromSameQuiz(pastHistory, newHistory)) {
|
||||
history = newHistory;
|
||||
return buildResult(newHistory.history);
|
||||
} else {
|
||||
history = pastHistory;
|
||||
return buildResult(pastHistory.history);
|
||||
}
|
||||
|
||||
return {
|
||||
messages: [SYSTEM_INSTRUCTION_MESSAGE, ...history.history, message],
|
||||
saveResponse(response: string) {
|
||||
// Register the conversation
|
||||
if (config.history) {
|
||||
history.history.push(message);
|
||||
history.history.push({ role: 'assistant', content: response });
|
||||
sessionStorage.moodleGPTHistory = JSON.stringify(history);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default getContentWithHistory;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import type Config from '../types/config';
|
||||
import type GPTAnswer from '../types/gpt-answer';
|
||||
import normalizeText from 'background/utils/normalize-text';
|
||||
import getContentWithHistory from './get-content-with-history';
|
||||
import OpenAI from 'openai';
|
||||
import { fixeO } from '../utils/fixe-o';
|
||||
import { QuestionSchemas } from './utils/question-schemas';
|
||||
import { MoodleQuestionType, MoodleQuestionResponse } from '../types/question-types';
|
||||
|
||||
/**
|
||||
* Get the response from chatGPT api
|
||||
@@ -29,27 +30,58 @@ async function getChatGPTResponse(
|
||||
dangerouslyAllowBrowser: true
|
||||
});
|
||||
|
||||
const req = await client.chat.completions.create(
|
||||
fixeO(config.model, {
|
||||
model: config.model,
|
||||
messages: contentHandler.messages,
|
||||
const questionType = contentHandler.query
|
||||
? contentHandler.query.question_type
|
||||
: MoodleQuestionType.UNKNOWN;
|
||||
const targetSchema: MoodleQuestionResponse =
|
||||
questionType !== MoodleQuestionType.UNKNOWN ? QuestionSchemas[questionType] : undefined;
|
||||
|
||||
max_completion_tokens: config.maxTokens || 2000 // Maximum length of the response,
|
||||
}),
|
||||
{ signal: config.timeout ? controller.signal : null }
|
||||
);
|
||||
const requestPayload: any = {
|
||||
model: config.model,
|
||||
messages: contentHandler.messages.map(msg => ({ ...msg })),
|
||||
max_completion_tokens: config.maxTokens || 2000 // Maximum length of the response,
|
||||
};
|
||||
|
||||
if (targetSchema) {
|
||||
requestPayload.response_format = {
|
||||
type: 'json_object'
|
||||
};
|
||||
|
||||
if (requestPayload.messages.length > 0 && requestPayload.messages[0].role === 'system') {
|
||||
requestPayload.messages[0].content += `\n\nYou MUST respond in JSON strictly adhering to the following schema. Do NOT wrap the JSON in markdown code blocks. Output raw JSON only.\n\n${JSON.stringify(targetSchema, null, 2)}`;
|
||||
}
|
||||
}
|
||||
|
||||
const req = await client.chat.completions.create(fixeO(config.model, requestPayload), {
|
||||
signal: config.timeout ? controller.signal : null
|
||||
});
|
||||
|
||||
clearTimeout(timeoutControler);
|
||||
|
||||
const response = req.choices[0].message.content ?? '';
|
||||
const rawResponse = req.choices[0].message.content ?? '';
|
||||
let structuredResponse: MoodleQuestionResponse | null = null;
|
||||
|
||||
if (targetSchema) {
|
||||
try {
|
||||
const cleanedResponse = rawResponse
|
||||
.replace(/^```(json)?[\s\S]*?\n([\s\S]*?)```$/g, '$2')
|
||||
.replace(/^```(json)?|```$/gm, '')
|
||||
.trim();
|
||||
structuredResponse = JSON.parse(cleanedResponse);
|
||||
} catch (e) {
|
||||
console.error('Failed to parse structured JSON from GPT', e);
|
||||
}
|
||||
}
|
||||
|
||||
// Save the response into the history
|
||||
if (typeof contentHandler.saveResponse === 'function') contentHandler.saveResponse(response);
|
||||
if (typeof contentHandler.saveResponse === 'function') {
|
||||
contentHandler.saveResponse(rawResponse);
|
||||
}
|
||||
|
||||
return {
|
||||
question,
|
||||
response,
|
||||
normalizedResponse: normalizeText(response)
|
||||
questionQuery: contentHandler.query,
|
||||
response: structuredResponse,
|
||||
rawResponse: rawResponse
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
import { MoodleQuestionQuery, MoodleQuestionType, AnswerOption } from '../types/question-types';
|
||||
import normalizeText from 'background/utils/normalize-text';
|
||||
|
||||
/**
|
||||
* Extracts options from a multichoice question.
|
||||
*/
|
||||
function extractOptions(questionElement: HTMLElement, inputSelector: string): AnswerOption[] {
|
||||
const options: AnswerOption[] = [];
|
||||
const inputs = questionElement.querySelectorAll<HTMLInputElement>(inputSelector);
|
||||
|
||||
inputs.forEach((input, index) => {
|
||||
// some inputs have value "-1", which is standard moodle for "clear choice"
|
||||
if (input.value === '-1') return;
|
||||
|
||||
// Try finding the label by ID
|
||||
let text = '';
|
||||
const labelEl = questionElement.querySelector(`#${input.id.replace(/:/g, '\\:')}_label`);
|
||||
if (labelEl) {
|
||||
text = labelEl.textContent ?? '';
|
||||
} else {
|
||||
text = input.parentElement?.textContent ?? '';
|
||||
}
|
||||
|
||||
text = normalizeText(text.replace('Clear my choice', ''));
|
||||
if (text) {
|
||||
options.push({
|
||||
index,
|
||||
text
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the question DOM element to determine the strict question type and structural query
|
||||
* @param questionElement The question container element
|
||||
* @param normalizedQuestionText The full text of the question
|
||||
* @returns MoodleQuestionQuery object or null if parsing fails
|
||||
*/
|
||||
export function parseMoodleQuestion(
|
||||
questionElement: HTMLElement,
|
||||
normalizedQuestionText: string
|
||||
): MoodleQuestionQuery | null {
|
||||
const container =
|
||||
questionElement.closest('.que') || questionElement.closest('.formulation') || questionElement;
|
||||
|
||||
if (container.classList.contains('multichoice')) {
|
||||
const checkboxes = container.querySelectorAll<HTMLInputElement>(
|
||||
'.answer input[type="checkbox"]'
|
||||
);
|
||||
const radios = container.querySelectorAll<HTMLInputElement>('.answer input[type="radio"]');
|
||||
|
||||
if (checkboxes.length > 0) {
|
||||
return {
|
||||
question_type: MoodleQuestionType.MULTIPLE_CHOICE,
|
||||
question_text: normalizedQuestionText,
|
||||
answer_options: extractOptions(container as HTMLElement, '.answer input[type="checkbox"]')
|
||||
};
|
||||
} else if (radios.length > 0) {
|
||||
return {
|
||||
question_type: MoodleQuestionType.SINGLE_CHOICE,
|
||||
question_text: normalizedQuestionText,
|
||||
answer_options: extractOptions(container as HTMLElement, '.answer input[type="radio"]')
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (container.classList.contains('truefalse')) {
|
||||
return {
|
||||
question_type: MoodleQuestionType.TRUE_FALSE,
|
||||
question_text: normalizedQuestionText
|
||||
};
|
||||
}
|
||||
|
||||
if (container.classList.contains('shortanswer')) {
|
||||
return {
|
||||
question_type: MoodleQuestionType.SHORT_TEXT,
|
||||
question_text: normalizedQuestionText
|
||||
};
|
||||
}
|
||||
|
||||
if (container.classList.contains('numerical')) {
|
||||
return {
|
||||
question_type: MoodleQuestionType.NUMERICAL,
|
||||
question_text: normalizedQuestionText
|
||||
};
|
||||
}
|
||||
|
||||
if (container.classList.contains('essay')) {
|
||||
return {
|
||||
question_type: MoodleQuestionType.ESSAY,
|
||||
question_text: normalizedQuestionText
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -29,18 +29,23 @@ function handleAtto(
|
||||
const textContainer = iframeBody.querySelector('p');
|
||||
if (!textContainer) return false;
|
||||
|
||||
const answerText =
|
||||
gptAnswer.response && 'correct_answer' in gptAnswer.response
|
||||
? String((gptAnswer.response as any).correct_answer)
|
||||
: gptAnswer.rawResponse;
|
||||
|
||||
if (config.typing) {
|
||||
let index = 0;
|
||||
const eventHandler = function (event: KeyboardEvent) {
|
||||
event.preventDefault();
|
||||
|
||||
if (event.key === 'Backspace' || index >= gptAnswer.response.length) {
|
||||
if (event.key === 'Backspace' || index >= answerText.length) {
|
||||
iframe.contentWindow!.removeEventListener('keydown', eventHandler);
|
||||
return;
|
||||
}
|
||||
|
||||
// Append text one character at a time
|
||||
const textNode = document.createTextNode(gptAnswer.response.charAt(index++));
|
||||
const textNode = document.createTextNode(answerText.charAt(index++));
|
||||
textContainer.appendChild(textNode);
|
||||
|
||||
// Move the cursor after the last character
|
||||
@@ -58,7 +63,7 @@ function handleAtto(
|
||||
|
||||
iframe.contentWindow.addEventListener('keydown', eventHandler);
|
||||
} else {
|
||||
textContainer.textContent += gptAnswer.response;
|
||||
textContainer.textContent += answerText;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -3,6 +3,7 @@ import type GPTAnswer from '../../types/gpt-answer';
|
||||
import Logs from 'background/utils/logs';
|
||||
import normalizeText from 'background/utils/normalize-text';
|
||||
import { pickBestReponse } from 'background/utils/pick-best-response';
|
||||
import { MoodleQuestionType, MultipleChoiceResponse } from '../../types/question-types';
|
||||
|
||||
/**
|
||||
* Handle input checkbox elements
|
||||
@@ -22,29 +23,52 @@ function handleCheckbox(
|
||||
return false;
|
||||
}
|
||||
|
||||
const corrects = gptAnswer.normalizedResponse.split('\n');
|
||||
|
||||
const possibleAnswers = Array.from(inputList)
|
||||
.map(inp => ({
|
||||
element: inp as HTMLInputElement,
|
||||
value: normalizeText(inp?.parentElement?.textContent ?? '')
|
||||
}))
|
||||
.filter(obj => obj.value !== '');
|
||||
|
||||
// Find the best answers elements
|
||||
const correctElements: Set<HTMLInputElement> = new Set();
|
||||
for (const correct of corrects) {
|
||||
const bestAnswer = pickBestReponse(correct, possibleAnswers);
|
||||
|
||||
if (config.logs && bestAnswer.value) {
|
||||
Logs.bestAnswer(bestAnswer.value, bestAnswer.similarity);
|
||||
// New structured mode
|
||||
if (
|
||||
gptAnswer.response &&
|
||||
gptAnswer.response.question_type === MoodleQuestionType.MULTIPLE_CHOICE
|
||||
) {
|
||||
const response = gptAnswer.response as MultipleChoiceResponse;
|
||||
const correctIndexes = new Set(response.correct_answer.indexes);
|
||||
|
||||
Array.from(inputList).forEach((inp, index) => {
|
||||
const element = inp as HTMLInputElement;
|
||||
if (correctIndexes.has(index)) {
|
||||
correctElements.add(element);
|
||||
}
|
||||
});
|
||||
|
||||
if (config.logs) {
|
||||
console.log('Using strict mode multiple choice selection:', response.correct_answer.indexes);
|
||||
}
|
||||
}
|
||||
// Fallback to fuzzy text matching if structural failed
|
||||
else {
|
||||
const corrects = gptAnswer.rawResponse.split('\n');
|
||||
|
||||
correctElements.add(bestAnswer.element as HTMLInputElement);
|
||||
const possibleAnswers = Array.from(inputList)
|
||||
.map(inp => ({
|
||||
element: inp as HTMLInputElement,
|
||||
value: normalizeText(inp?.parentElement?.textContent ?? '')
|
||||
}))
|
||||
.filter(obj => obj.value !== '');
|
||||
|
||||
for (const correct of corrects) {
|
||||
const bestAnswer = pickBestReponse(correct, possibleAnswers);
|
||||
|
||||
if (config.logs && bestAnswer.value) {
|
||||
Logs.bestAnswer(bestAnswer.value, bestAnswer.similarity);
|
||||
}
|
||||
|
||||
correctElements.add(bestAnswer.element as HTMLInputElement);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if it should be checked or not
|
||||
for (const element of possibleAnswers.map(e => e.element)) {
|
||||
for (const inp of Array.from(inputList)) {
|
||||
const element = inp as HTMLInputElement;
|
||||
const needAction =
|
||||
(element.checked && !correctElements.has(element)) ||
|
||||
(!element.checked && correctElements.has(element));
|
||||
|
||||
@@ -27,18 +27,23 @@ function handleContentEditable(
|
||||
return false;
|
||||
}
|
||||
|
||||
const answerText =
|
||||
gptAnswer.response && 'correct_answer' in gptAnswer.response
|
||||
? String((gptAnswer.response as any).correct_answer)
|
||||
: gptAnswer.rawResponse;
|
||||
|
||||
if (config.typing) {
|
||||
let index = 0;
|
||||
|
||||
const eventHandler = function (event: KeyboardEvent) {
|
||||
event.preventDefault();
|
||||
|
||||
if (event.key === 'Backspace' || index >= gptAnswer.response.length) {
|
||||
if (event.key === 'Backspace' || index >= answerText.length) {
|
||||
input.removeEventListener('keydown', eventHandler);
|
||||
return;
|
||||
}
|
||||
|
||||
input.textContent = gptAnswer.response.slice(0, ++index);
|
||||
input.textContent = answerText.slice(0, ++index);
|
||||
|
||||
// Put the cursor at the end of the typed text
|
||||
input.focus();
|
||||
@@ -54,7 +59,7 @@ function handleContentEditable(
|
||||
|
||||
input.addEventListener('keydown', eventHandler);
|
||||
} else {
|
||||
input.textContent = gptAnswer.response;
|
||||
input.textContent = answerText;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -22,7 +22,11 @@ function handleNumber(
|
||||
return false;
|
||||
}
|
||||
|
||||
const number = gptAnswer.normalizedResponse.match(/\d+([,.]\d+)?/gi)?.[0]?.replace(',', '.');
|
||||
const rawNumberStr =
|
||||
gptAnswer.response && 'correct_answer' in gptAnswer.response
|
||||
? String((gptAnswer.response as any).correct_answer)
|
||||
: gptAnswer.rawResponse;
|
||||
const number = rawNumberStr.match(/\d+([,.]\d+)?/gi)?.[0]?.replace(',', '.');
|
||||
|
||||
if (number === undefined) return false;
|
||||
|
||||
|
||||
@@ -3,6 +3,11 @@ import type GPTAnswer from '../../types/gpt-answer';
|
||||
import Logs from 'background/utils/logs';
|
||||
import normalizeText from 'background/utils/normalize-text';
|
||||
import { pickBestReponse } from 'background/utils/pick-best-response';
|
||||
import {
|
||||
MoodleQuestionType,
|
||||
SingleChoiceResponse,
|
||||
TrueFalseResponse
|
||||
} from '../../types/question-types';
|
||||
|
||||
/**
|
||||
* Handle input radio elements
|
||||
@@ -22,26 +27,60 @@ function handleRadio(
|
||||
return false;
|
||||
}
|
||||
|
||||
const possibleAnswers = Array.from(inputList)
|
||||
.map(inp => ({
|
||||
element: inp,
|
||||
value: normalizeText(inp?.parentElement?.textContent ?? '')
|
||||
}))
|
||||
.filter(obj => obj.value !== '');
|
||||
let correctInput: HTMLInputElement | null = null;
|
||||
|
||||
const bestAnswer = pickBestReponse(gptAnswer.normalizedResponse, possibleAnswers);
|
||||
if (gptAnswer.response && gptAnswer.response.question_type === MoodleQuestionType.SINGLE_CHOICE) {
|
||||
const res = gptAnswer.response as SingleChoiceResponse;
|
||||
const index = res.correct_answer.index;
|
||||
if (index >= 0 && index < inputList.length) {
|
||||
correctInput = inputList[index] as HTMLInputElement;
|
||||
}
|
||||
} else if (
|
||||
gptAnswer.response &&
|
||||
gptAnswer.response.question_type === MoodleQuestionType.TRUE_FALSE
|
||||
) {
|
||||
const res = gptAnswer.response as TrueFalseResponse;
|
||||
// In Moodle true/false typically true is index 0 and false is index 1 or vice-versa.
|
||||
// The query extracted options though... wait! True/false doesn't use `extractOptions`!
|
||||
// So we need to match text "true" or "false", or 1/0.
|
||||
const isTrue = res.correct_answer === true;
|
||||
|
||||
if (config.logs && bestAnswer.value) {
|
||||
Logs.bestAnswer(bestAnswer.value, bestAnswer.similarity);
|
||||
// Quick fallback fuzzy to "true" or "false" if we don't know the exact indices
|
||||
// True/false has radio options, so we can search by text.
|
||||
const possibleAnswers = Array.from(inputList)
|
||||
.map(inp => ({
|
||||
element: inp,
|
||||
value: normalizeText(inp?.parentElement?.textContent ?? '')
|
||||
}))
|
||||
.filter(obj => obj.value !== '');
|
||||
|
||||
const bestAnswer = pickBestReponse(isTrue ? 'true' : 'false', possibleAnswers);
|
||||
correctInput = bestAnswer.element as HTMLInputElement;
|
||||
} else {
|
||||
// Fallback parsing
|
||||
const possibleAnswers = Array.from(inputList)
|
||||
.map(inp => ({
|
||||
element: inp,
|
||||
value: normalizeText(inp?.parentElement?.textContent ?? '')
|
||||
}))
|
||||
.filter(obj => obj.value !== '');
|
||||
|
||||
const bestAnswer = pickBestReponse(gptAnswer.rawResponse, possibleAnswers);
|
||||
|
||||
if (config.logs && bestAnswer.value) {
|
||||
Logs.bestAnswer(bestAnswer.value, bestAnswer.similarity);
|
||||
}
|
||||
correctInput = bestAnswer.element as HTMLInputElement;
|
||||
}
|
||||
|
||||
const correctInput = bestAnswer.element as HTMLInputElement;
|
||||
if (config.mouseover) {
|
||||
correctInput.addEventListener('mouseover', () => correctInput.click(), {
|
||||
once: true
|
||||
});
|
||||
} else {
|
||||
correctInput.click();
|
||||
if (correctInput) {
|
||||
if (config.mouseover) {
|
||||
correctInput.addEventListener('mouseover', () => (correctInput as HTMLInputElement).click(), {
|
||||
once: true
|
||||
});
|
||||
} else {
|
||||
correctInput.click();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -18,7 +18,12 @@ function handleSelect(
|
||||
): boolean {
|
||||
if (inputList.length === 0 || inputList[0].tagName !== 'SELECT') return false;
|
||||
|
||||
const corrects = gptAnswer.normalizedResponse.split('\n');
|
||||
const rawResponse =
|
||||
gptAnswer.response && 'correct_answer' in gptAnswer.response
|
||||
? String((gptAnswer.response as any).correct_answer)
|
||||
: gptAnswer.rawResponse;
|
||||
|
||||
const corrects = rawResponse.split('\n');
|
||||
|
||||
if (config.logs) Logs.array(corrects);
|
||||
|
||||
|
||||
@@ -22,23 +22,28 @@ function handleTextbox(
|
||||
return false;
|
||||
}
|
||||
|
||||
const answerText =
|
||||
gptAnswer.response && 'correct_answer' in gptAnswer.response
|
||||
? String((gptAnswer.response as any).correct_answer)
|
||||
: gptAnswer.rawResponse;
|
||||
|
||||
if (config.typing) {
|
||||
let index = 0;
|
||||
|
||||
const eventHandler = function (event: Event) {
|
||||
event.preventDefault();
|
||||
|
||||
if ((<KeyboardEvent>event).key === 'Backspace' || index >= gptAnswer.response.length) {
|
||||
if ((<KeyboardEvent>event).key === 'Backspace' || index >= answerText.length) {
|
||||
input.removeEventListener('keydown', eventHandler);
|
||||
return;
|
||||
}
|
||||
|
||||
input.value = gptAnswer.response.slice(0, ++index);
|
||||
input.value = answerText.slice(0, ++index);
|
||||
};
|
||||
|
||||
input.addEventListener('keydown', eventHandler);
|
||||
} else {
|
||||
input.value = gptAnswer.response;
|
||||
input.value = answerText;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
import { MoodleQuestionType } from '../../types/question-types';
|
||||
|
||||
export const QuestionSchemas: Record<MoodleQuestionType, any> = {
|
||||
[MoodleQuestionType.SINGLE_CHOICE]: {
|
||||
name: 'single_choice_response',
|
||||
strict: true,
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
question_type: { type: 'string', enum: [MoodleQuestionType.SINGLE_CHOICE] },
|
||||
correct_answer: {
|
||||
type: 'object',
|
||||
properties: { index: { type: 'integer' } },
|
||||
required: ['index'],
|
||||
additionalProperties: false
|
||||
}
|
||||
},
|
||||
required: ['question_type', 'correct_answer'],
|
||||
additionalProperties: false
|
||||
}
|
||||
},
|
||||
[MoodleQuestionType.MULTIPLE_CHOICE]: {
|
||||
name: 'multiple_choice_response',
|
||||
strict: true,
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
question_type: { type: 'string', enum: [MoodleQuestionType.MULTIPLE_CHOICE] },
|
||||
correct_answer: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
indexes: {
|
||||
type: 'array',
|
||||
items: { type: 'integer' }
|
||||
}
|
||||
},
|
||||
required: ['indexes'],
|
||||
additionalProperties: false
|
||||
}
|
||||
},
|
||||
required: ['question_type', 'correct_answer'],
|
||||
additionalProperties: false
|
||||
}
|
||||
},
|
||||
[MoodleQuestionType.TRUE_FALSE]: {
|
||||
name: 'true_false_response',
|
||||
strict: true,
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
question_type: { type: 'string', enum: [MoodleQuestionType.TRUE_FALSE] },
|
||||
correct_answer: { type: 'boolean' }
|
||||
},
|
||||
required: ['question_type', 'correct_answer'],
|
||||
additionalProperties: false
|
||||
}
|
||||
},
|
||||
[MoodleQuestionType.SHORT_TEXT]: {
|
||||
name: 'short_text_response',
|
||||
strict: true,
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
question_type: { type: 'string', enum: [MoodleQuestionType.SHORT_TEXT] },
|
||||
correct_answer: { type: 'string' }
|
||||
},
|
||||
required: ['question_type', 'correct_answer'],
|
||||
additionalProperties: false
|
||||
}
|
||||
},
|
||||
[MoodleQuestionType.NUMERICAL]: {
|
||||
name: 'numerical_response',
|
||||
strict: true,
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
question_type: { type: 'string', enum: [MoodleQuestionType.NUMERICAL] },
|
||||
correct_answer: { type: 'number' }
|
||||
},
|
||||
required: ['question_type', 'correct_answer'],
|
||||
additionalProperties: false
|
||||
}
|
||||
},
|
||||
[MoodleQuestionType.ESSAY]: {
|
||||
name: 'essay_response',
|
||||
strict: true,
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
question_type: { type: 'string', enum: [MoodleQuestionType.ESSAY] },
|
||||
correct_answer: { type: 'string' }
|
||||
},
|
||||
required: ['question_type', 'correct_answer'],
|
||||
additionalProperties: false
|
||||
}
|
||||
},
|
||||
[MoodleQuestionType.UNKNOWN]: undefined
|
||||
};
|
||||
@@ -1,7 +1,9 @@
|
||||
import { MoodleQuestionQuery, MoodleQuestionResponse } from './question-types';
|
||||
|
||||
type GPTAnswer = {
|
||||
question: string;
|
||||
response: string;
|
||||
normalizedResponse: string;
|
||||
questionQuery: MoodleQuestionQuery | null;
|
||||
response: MoodleQuestionResponse | null;
|
||||
rawResponse: string; // Keep the original just in case or for logging/unknown
|
||||
};
|
||||
|
||||
export default GPTAnswer;
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
export enum MoodleQuestionType {
|
||||
SINGLE_CHOICE = 'single_choice',
|
||||
MULTIPLE_CHOICE = 'multiple_choice',
|
||||
TRUE_FALSE = 'true_false',
|
||||
SHORT_TEXT = 'short_text',
|
||||
NUMERICAL = 'numerical',
|
||||
ESSAY = 'essay',
|
||||
UNKNOWN = 'unknown'
|
||||
}
|
||||
|
||||
export interface AnswerOption {
|
||||
index: number;
|
||||
text: string;
|
||||
}
|
||||
|
||||
// ==== Queries sent to LLM ====
|
||||
|
||||
export interface SingleChoiceQuery {
|
||||
question_type: MoodleQuestionType.SINGLE_CHOICE;
|
||||
question_text: string;
|
||||
answer_options: AnswerOption[];
|
||||
}
|
||||
|
||||
export interface MultipleChoiceQuery {
|
||||
question_type: MoodleQuestionType.MULTIPLE_CHOICE;
|
||||
question_text: string;
|
||||
answer_options: AnswerOption[];
|
||||
}
|
||||
|
||||
export interface TrueFalseQuery {
|
||||
question_type: MoodleQuestionType.TRUE_FALSE;
|
||||
question_text: string;
|
||||
}
|
||||
|
||||
export interface ShortTextQuery {
|
||||
question_type: MoodleQuestionType.SHORT_TEXT;
|
||||
question_text: string;
|
||||
}
|
||||
|
||||
export interface NumericalQuery {
|
||||
question_type: MoodleQuestionType.NUMERICAL;
|
||||
question_text: string;
|
||||
}
|
||||
|
||||
export interface EssayQuery {
|
||||
question_type: MoodleQuestionType.ESSAY;
|
||||
question_text: string;
|
||||
}
|
||||
|
||||
export type MoodleQuestionQuery =
|
||||
| SingleChoiceQuery
|
||||
| MultipleChoiceQuery
|
||||
| TrueFalseQuery
|
||||
| ShortTextQuery
|
||||
| NumericalQuery
|
||||
| EssayQuery;
|
||||
|
||||
// ==== Expected LLM Responses ====
|
||||
|
||||
export interface SingleChoiceResponse {
|
||||
question_type: MoodleQuestionType.SINGLE_CHOICE;
|
||||
correct_answer: { index: number };
|
||||
}
|
||||
|
||||
export interface MultipleChoiceResponse {
|
||||
question_type: MoodleQuestionType.MULTIPLE_CHOICE;
|
||||
correct_answer: { indexes: number[] };
|
||||
}
|
||||
|
||||
export interface TrueFalseResponse {
|
||||
question_type: MoodleQuestionType.TRUE_FALSE;
|
||||
correct_answer: boolean;
|
||||
}
|
||||
|
||||
export interface ShortTextResponse {
|
||||
question_type: MoodleQuestionType.SHORT_TEXT;
|
||||
correct_answer: string;
|
||||
}
|
||||
|
||||
export interface NumericalResponse {
|
||||
question_type: MoodleQuestionType.NUMERICAL;
|
||||
correct_answer: number;
|
||||
}
|
||||
|
||||
export interface EssayResponse {
|
||||
question_type: MoodleQuestionType.ESSAY;
|
||||
correct_answer: string;
|
||||
}
|
||||
|
||||
export type MoodleQuestionResponse =
|
||||
| SingleChoiceResponse
|
||||
| MultipleChoiceResponse
|
||||
| TrueFalseResponse
|
||||
| ShortTextResponse
|
||||
| NumericalResponse
|
||||
| EssayResponse;
|
||||
+2
-1
@@ -2,7 +2,8 @@
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"baseUrl": "src",
|
||||
"module": "esnext",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"target": "ES6",
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
|
||||
Reference in New Issue
Block a user