From b609e9c76063e2aee259b3019cc549ebfcce4f50 Mon Sep 17 00:00:00 2001 From: aadhakal <33aashish.dhakal@gmail.com> Date: Tue, 13 Jan 2026 09:57:25 -0500 Subject: [PATCH 01/10] Add star rating component for AI draft feedback - Implemented 5-star rating system for AI draft quality assessment - Added @tracked aiDraftRating property to store rating (1-5) - Created starDefinitions array with rating values and tooltip descriptions: * 1 star: Not usable - requires complete rewrite * 2 stars: Has significant errors or disconnects that prevent use * 3 stars: Usable but requires editing before sending * 4 stars: Ready to use - could be sent as is * 5 stars: Exceptional quality - exceeds expectations - Added isStarFilled() action to determine filled/empty star state - Modified canBringDown getter to require rating before enabling button - Added showRatingControls getter to hide controls after draft is used - Reset aiDraftRating when generating new AI draft - Used {{#each}} loop in template to reduce repetitive star markup - Star rating is required before user can bring AI draft down to editor - Also minimized the line break for existing and AI text separator --- app/components/response-new.hbs | 36 +++++++++++++++++++++++------- app/components/response-new.js | 39 +++++++++++++++++++++++++++++---- 2 files changed, 63 insertions(+), 12 deletions(-) diff --git a/app/components/response-new.hbs b/app/components/response-new.hbs index 508e5d6c2..72713fb78 100644 --- a/app/components/response-new.hbs +++ b/app/components/response-new.hbs @@ -35,14 +35,34 @@ /> - + {{! Star Rating and Button Row }} + {{#if this.showRatingControls}} +
+
+ +
+ {{#each this.starDefinitions as |star|}} + + {{/each}} +
+
+ + +
+ {{/if}} {{/if}} diff --git a/app/components/response-new.js b/app/components/response-new.js index 2bba7bfb6..bb8265d7e 100644 --- a/app/components/response-new.js +++ b/app/components/response-new.js @@ -36,6 +36,18 @@ export default class ResponseNewComponent extends Component { @tracked doShowLoadingMessage = false; @tracked quillEditorKey = 0; @tracked pendingContent = null; + @tracked aiDraftRating = null; + + starDefinitions = [ + { value: 1, tooltip: 'Not usable - requires complete rewrite' }, + { + value: 2, + tooltip: 'Has significant errors or disconnects that prevent use', + }, + { value: 3, tooltip: 'Usable but requires editing before sending' }, + { value: 4, tooltip: 'Ready to use - could be sent as is' }, + { value: 5, tooltip: 'Exceptional quality - exceeds expectations' }, + ]; doUseOnlyOwnMarkup = true; maxResponseLength = 14680064; @@ -257,9 +269,13 @@ export default class ResponseNewComponent extends Component { } get aiButtonDisabled() { - return ( - !this.hasSubmission || !this.aiDraft.hasStudentWork(this.actualSubmission) - ); + const hasSubmission = this.hasSubmission; + const actualSubmission = this.actualSubmission; + const hasWork = actualSubmission + ? this.aiDraft.hasStudentWork(actualSubmission) + : false; + + return !hasSubmission || !hasWork; } get isValidQuillContent() { @@ -271,9 +287,18 @@ export default class ResponseNewComponent extends Component { } get canBringDown() { + return !this.hasUsedAIDraft && this.aiDraftRating !== null; + } + + get showRatingControls() { return !this.hasUsedAIDraft; } + @action + isStarFilled(starNumber) { + return this.aiDraftRating !== null && this.aiDraftRating >= starNumber; + } + quote(string, opts, isImageTag) { string = string.replace(/(\r\n|\n|\r)/gm, ' '); let defaultPrefix = ' '; @@ -630,6 +655,7 @@ export default class ResponseNewComponent extends Component { // Convert AI plain text to HTML immediately and store it this.aiGeneratedText = this.convertPlainTextToHtml(draft); this.hasUsedAIDraft = false; // Reset "Bring it Down" button + this.aiDraftRating = null; // Reset rating for new draft this.alert.showToast( 'success', @@ -666,7 +692,7 @@ export default class ResponseNewComponent extends Component { // Get current editor content (HTML format) const currentText = this.quillText || ''; - const separator = currentText.trim() ? '



' : ''; + const separator = currentText.trim() ? '


' : ''; // Combine HTML const newText = currentText + separator + this.aiGeneratedText; @@ -694,4 +720,9 @@ export default class ResponseNewComponent extends Component { null ); } + + @action + setStarRating(rating) { + this.aiDraftRating = rating; + } } From 3ea8e8c9cd985a51b6c07dee352755fd73121f81 Mon Sep 17 00:00:00 2001 From: aadhakal <33aashish.dhakal@gmail.com> Date: Tue, 13 Jan 2026 09:58:02 -0500 Subject: [PATCH 02/10] Add star rating styles with custom hover tooltips - Styled stars with amber filled (#f5af4e) and gray empty (#ddd) colors - Created horizontal layout (stars + button side-by-side) with flexbox - Implemented custom CSS tooltips using ::after pseudo-element - Tooltips display with paddy green background (#8BC34A) - Positioned tooltips above stars with proper centering - Added required asterisk indicator in red on rating label - Optimized CSS by removing unnecessary transitions and redundant properties - Stars are 24px for adequate touch target size - Compact layout with star-rating-container fitting content width --- app/styles/_response-mentor-thread.scss | 74 ++++++++++++++++++++++--- 1 file changed, 66 insertions(+), 8 deletions(-) diff --git a/app/styles/_response-mentor-thread.scss b/app/styles/_response-mentor-thread.scss index bcf7ac418..199d2d102 100644 --- a/app/styles/_response-mentor-thread.scss +++ b/app/styles/_response-mentor-thread.scss @@ -205,21 +205,79 @@ cursor: default; } } + + // Star Rating and Button Row + .ai-draft-actions { + display: flex; + align-items: center; + gap: 1em; + } + + .star-rating-container { + display: flex; + align-items: center; + gap: 1em; + padding: 0.8em; + background-color: #f5f5f5; + border-radius: 6px; + + .star-rating-label { + font-weight: 500; + margin: 0; + + .required-asterisk { + color: #d9534f; + } + } + + .star-rating-stars { + display: flex; + gap: 0.3em; + + .star-icon { + font-size: 24px; + cursor: pointer; + position: relative; + + &.star-filled { + color: #f5af4e; + } + + &.star-empty { + color: #ddd; + } + + &:hover { + color: #f5af4e; + + &::after { + content: attr(title); + position: absolute; + bottom: 100%; + left: 50%; + transform: translateX(-50%); + padding: 8px 12px; + background-color: #8BC34A; + color: white; + font-size: 13px; + white-space: nowrap; + border-radius: 4px; + margin-bottom: 5px; + z-index: 1000; + pointer-events: none; + } + } + } + } + } + .ai-bring-down-btn { - // Inherit base button styles from theme @extend .primary-button; - padding: 0.5em 1em; - font-size: 0.95em; - &:disabled { background-color: #cccccc; cursor: not-allowed; opacity: 0.6; - - &:hover { - background-color: #cccccc; // Override hover state when disabled - } } } } From bf429e7dee8a138cbaaa7150cc5ce93d7fa5b256 Mon Sep 17 00:00:00 2001 From: aadhakal <33aashish.dhakal@gmail.com> Date: Tue, 13 Jan 2026 10:08:59 -0500 Subject: [PATCH 03/10] Fix "Draft From AI" button disabled state after page refresh - Added `await submission.answer` in model hook to preload answer relationship - Wrapped in try/catch since non-VMT submissions may not have answers - For VMT submissions, answer relationship wasn't loaded into Ember Data cache - Service uses peekRecord() which only checks cache, doesn't trigger load - Button now works immediately after page refresh without requiring user interaction Bug: Button stayed disabled after refresh even with student work present Root cause: Answer relationship not preloaded for some submissions --- app/routes/responses/new/submission.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/routes/responses/new/submission.js b/app/routes/responses/new/submission.js index 53f658923..8093452c1 100644 --- a/app/routes/responses/new/submission.js +++ b/app/routes/responses/new/submission.js @@ -97,6 +97,13 @@ export default class ResponsesNewSubmissionRoute extends Route { { reload: true } ); + // Load answer relationship for VMT submissions (needed for AI draft) + try { + await submission.answer; + } catch (e) { + // Answer might not exist for non-VMT submissions, that's okay + } + // Early return if draft exists const draftResponse = this._findDraftResponse( allResponses, From 9913d7284ef49342571ab4408021a68bbaffa6e7 Mon Sep 17 00:00:00 2001 From: aadhakal <33aashish.dhakal@gmail.com> Date: Wed, 14 Jan 2026 10:27:27 -0500 Subject: [PATCH 04/10] Add usage survey checkboxes to AI draft workflow - Added @tracked properties for checkbox states: * showUsageCheckboxes - controls visibility of checkbox UI * usageNotForStudents - won't use with students * usageNotForSelf - won't use for personal learning * usageForStudents - will use with students * usageToThinkAbout - will save for future consideration * usageFeedbackOnAI - will provide AI performance feedback - Created usageOptions array with checkbox keys and labels - Modified bringAiDraftDown() to show checkboxes instead of immediate copy - Added continueWithAIDraft() action to validate and process selections - Added hasSelectedUsageOption getter to ensure at least one checkbox selected - Updated showRatingControls to hide when checkboxes are visible - Checkbox selections logged (TODO: backend integration) before copying draft --- app/components/response-new.js | 62 +++++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/app/components/response-new.js b/app/components/response-new.js index bb8265d7e..1ff626217 100644 --- a/app/components/response-new.js +++ b/app/components/response-new.js @@ -30,6 +30,8 @@ export default class ResponseNewComponent extends Component { @tracked quillText = ''; @tracked isQuillEmpty = false; @tracked isQuillTooLong = false; + + // AI Draft and Logging related tracked properties @tracked originalText = ''; @tracked aiGeneratedText = null; @tracked hasUsedAIDraft = false; @@ -37,6 +39,12 @@ export default class ResponseNewComponent extends Component { @tracked quillEditorKey = 0; @tracked pendingContent = null; @tracked aiDraftRating = null; + @tracked showUsageCheckboxes = false; + @tracked usageNotForStudents = false; + @tracked usageNotForSelf = false; + @tracked usageForStudents = false; + @tracked usageToThinkAbout = false; + @tracked usageFeedbackOnAI = false; starDefinitions = [ { value: 1, tooltip: 'Not usable - requires complete rewrite' }, @@ -49,6 +57,30 @@ export default class ResponseNewComponent extends Component { { value: 5, tooltip: 'Exceptional quality - exceeds expectations' }, ]; + usageOptions = [ + { + key: 'usageNotForStudents', + label: 'I would/will not use this with students', + }, + { + key: 'usageNotForSelf', + label: + 'I will not use this for myself (learning about math, feedback, or my students)', + }, + { + key: 'usageForStudents', + label: 'I would/will use this with my students', + }, + { + key: 'usageToThinkAbout', + label: 'I will save this as something to think about', + }, + { + key: 'usageFeedbackOnAI', + label: 'I will use this to give feedback on the AI performance', + }, + ]; + doUseOnlyOwnMarkup = true; maxResponseLength = 14680064; @@ -291,7 +323,17 @@ export default class ResponseNewComponent extends Component { } get showRatingControls() { - return !this.hasUsedAIDraft; + return !this.hasUsedAIDraft && !this.showUsageCheckboxes; + } + + get hasSelectedUsageOption() { + return ( + this.usageNotForStudents || + this.usageNotForSelf || + this.usageForStudents || + this.usageToThinkAbout || + this.usageFeedbackOnAI + ); } @action @@ -689,6 +731,23 @@ export default class ResponseNewComponent extends Component { if (!this.canBringDown || !this.aiGeneratedText) { return; } + // Show usage checkboxes instead of immediately copying + this.showUsageCheckboxes = true; + } + + @action + continueWithAIDraft() { + if (!this.hasSelectedUsageOption) { + this.alert.showToast( + 'info', + 'Please select at least one option before continuing', + 'bottom-end', + 3000, + false, + null + ); + return; + } // Get current editor content (HTML format) const currentText = this.quillText || ''; @@ -710,6 +769,7 @@ export default class ResponseNewComponent extends Component { // Mark draft as used this.hasUsedAIDraft = true; + this.showUsageCheckboxes = false; this.alert.showToast( 'success', From eb5b833748a2db01ebbeb63cf16b7cea709b085c Mon Sep 17 00:00:00 2001 From: aadhakal <33aashish.dhakal@gmail.com> Date: Wed, 14 Jan 2026 10:28:54 -0500 Subject: [PATCH 05/10] Add checkbox survey UI to AI draft section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added conditional .ai-usage-checkboxes section after star rating - Displays when showUsageCheckboxes is true - Used {{#each this.usageOptions}} loop to render checkboxes dynamically - Each checkbox binds to tracked property via {{get this option.key}} - Added usage-prompt with required asterisk indicator - Included "Continue" button to proceed after selections - Validates at least one checkbox selected before allowing continuation - Compact template using array iteration reduces repetitive markup UI flow: Rate stars → Click "Bring it Down" → Select usage options → Click "Continue" → Content copied to editor --- app/components/response-new.hbs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/app/components/response-new.hbs b/app/components/response-new.hbs index 72713fb78..532ff89e7 100644 --- a/app/components/response-new.hbs +++ b/app/components/response-new.hbs @@ -63,6 +63,25 @@ {{/if}} + {{! Usage Checkboxes }} + {{#if this.showUsageCheckboxes}} +
+

How will you use this AI draft? * (Check all that apply)

+ {{#each this.usageOptions as |option|}} + + {{/each}} + +
+ {{/if}} {{/if}} From bde6e449702fcd74292d85f12753fe697a60e49a Mon Sep 17 00:00:00 2001 From: aadhakal <33aashish.dhakal@gmail.com> Date: Wed, 14 Jan 2026 10:29:34 -0500 Subject: [PATCH 06/10] Add modal-style checkbox survey to AI draft section - Created .ai-usage-checkboxes styles with light background (#f9f9f9) - Styled checkbox options with flexbox layout and proper spacing - Added usage-prompt header with bold font and required asterisk - Checkbox inputs have pointer cursor for better UX - Continue button has top margin for visual separation - Clean, compact design matching existing AI draft section styles --- app/styles/_response-mentor-thread.scss | 35 +++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/app/styles/_response-mentor-thread.scss b/app/styles/_response-mentor-thread.scss index 199d2d102..9cd59fed4 100644 --- a/app/styles/_response-mentor-thread.scss +++ b/app/styles/_response-mentor-thread.scss @@ -280,6 +280,41 @@ opacity: 0.6; } } + + // Usage Checkboxes + .ai-usage-checkboxes { + padding: 1em; + background-color: #f9f9f9; + border-radius: 6px; + border: 2px solid #e0e0e0; + + .usage-prompt { + font-weight: 600; + margin-bottom: 1em; + color: #333; + + .required-asterisk { + color: #d9534f; + } + } + + .usage-option { + display: flex; + align-items: center; + gap: 0.5em; + margin-bottom: 0.8em; + cursor: pointer; + font-size: 0.95em; + + input[type='checkbox'] { + cursor: pointer; + } + } + + .ai-continue-btn { + margin-top: 1em; + } + } } .new-response-header { From 9b191ab0a2bfc0224f351c3edc12c2b4c25ebe6f Mon Sep 17 00:00:00 2001 From: aadhakal <33aashish.dhakal@gmail.com> Date: Fri, 16 Jan 2026 12:11:49 -0500 Subject: [PATCH 07/10] Add written feedback requirement for AI draft feature - Add @tracked aiWrittenFeedback property to store user's written feedback - Update canBringDown getter to require minimum 10 characters of feedback - Validation: aiDraftRating !== null && aiWrittenFeedback.trim().length >= 10 - Implement updateAiWrittenFeedback(event) action following standard Ember pattern - Reset aiWrittenFeedback to empty string when generating new AI draft --- app/components/response-new.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/components/response-new.js b/app/components/response-new.js index 1ff626217..ea1c5add1 100644 --- a/app/components/response-new.js +++ b/app/components/response-new.js @@ -39,6 +39,7 @@ export default class ResponseNewComponent extends Component { @tracked quillEditorKey = 0; @tracked pendingContent = null; @tracked aiDraftRating = null; + @tracked aiWrittenFeedback = ''; @tracked showUsageCheckboxes = false; @tracked usageNotForStudents = false; @tracked usageNotForSelf = false; @@ -319,7 +320,11 @@ export default class ResponseNewComponent extends Component { } get canBringDown() { - return !this.hasUsedAIDraft && this.aiDraftRating !== null; + return ( + !this.hasUsedAIDraft && + this.aiDraftRating !== null && + this.aiWrittenFeedback.trim().length >= 10 + ); } get showRatingControls() { @@ -341,6 +346,11 @@ export default class ResponseNewComponent extends Component { return this.aiDraftRating !== null && this.aiDraftRating >= starNumber; } + @action + updateAiWrittenFeedback(event) { + this.aiWrittenFeedback = event.target.value; + } + quote(string, opts, isImageTag) { string = string.replace(/(\r\n|\n|\r)/gm, ' '); let defaultPrefix = ' '; @@ -698,6 +708,7 @@ export default class ResponseNewComponent extends Component { this.aiGeneratedText = this.convertPlainTextToHtml(draft); this.hasUsedAIDraft = false; // Reset "Bring it Down" button this.aiDraftRating = null; // Reset rating for new draft + this.aiWrittenFeedback = ''; // Reset written feedback for new draft this.alert.showToast( 'success', From 63bae1856afdc321ec2474a5032e74077fa0921d Mon Sep 17 00:00:00 2001 From: aadhakal <33aashish.dhakal@gmail.com> Date: Fri, 16 Jan 2026 12:12:11 -0500 Subject: [PATCH 08/10] Add written feedback textarea to AI draft section - Add written feedback textarea using Ember