diff --git a/app/components/response-new.hbs b/app/components/response-new.hbs
index 508e5d6c2..d9e839e00 100644
--- a/app/components/response-new.hbs
+++ b/app/components/response-new.hbs
@@ -35,14 +35,66 @@
/>
-
+ {{! Star Rating and Button Row }}
+
+
+ {{#if this.showRatingControls}}
+
+
+
+ {{#each this.starDefinitions as |star|}}
+
+ {{/each}}
+
+
+
+
+
+
+
+
+
+ {{/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}}
@@ -156,6 +208,8 @@
{{/if}}
+ {{!-- AI action section removed temporarily; will be restored on a dedicated branch --}}
+
{{#if this.doShowLoadingMessage}}
{{/if}}
@@ -179,4 +233,4 @@
>Save as Draft
-
\ No newline at end of file
+
diff --git a/app/components/response-new.js b/app/components/response-new.js
index 2bba7bfb6..6f35d1558 100644
--- a/app/components/response-new.js
+++ b/app/components/response-new.js
@@ -30,12 +30,58 @@ 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;
+ @tracked hasCopiedAiText = false;
@tracked doShowLoadingMessage = false;
@tracked quillEditorKey = 0;
@tracked pendingContent = null;
+ @tracked aiDraftRating = null;
+ @tracked aiWrittenFeedback = '';
+ @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' },
+ {
+ 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' },
+ ];
+
+ 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;
@@ -257,9 +303,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,7 +321,35 @@ export default class ResponseNewComponent extends Component {
}
get canBringDown() {
- return !this.hasUsedAIDraft;
+ return (
+ !this.hasUsedAIDraft &&
+ this.aiDraftRating !== null &&
+ this.aiWrittenFeedback.trim().length >= 10
+ );
+ }
+
+ get showRatingControls() {
+ return !this.hasUsedAIDraft && !this.showUsageCheckboxes;
+ }
+
+ get hasSelectedUsageOption() {
+ return (
+ this.usageNotForStudents ||
+ this.usageNotForSelf ||
+ this.usageForStudents ||
+ this.usageToThinkAbout ||
+ this.usageFeedbackOnAI
+ );
+ }
+
+ @action
+ isStarFilled(starNumber) {
+ return this.aiDraftRating !== null && this.aiDraftRating >= starNumber;
+ }
+
+ @action
+ updateAiWrittenFeedback(event) {
+ this.aiWrittenFeedback = event.target.value;
}
quote(string, opts, isImageTag) {
@@ -413,6 +491,7 @@ export default class ResponseNewComponent extends Component {
this[p] = !this[p];
}
+ @action
cleanupTrashedItems(response) {
response.selections?.forEach((selection) => {
if (selection.isTrashed) {
@@ -627,13 +706,17 @@ export default class ResponseNewComponent extends Component {
// Clear any pending content from previous "Bring it Down"
this.pendingContent = null;
- // Convert AI plain text to HTML immediately and store it
- this.aiGeneratedText = this.convertPlainTextToHtml(draft);
- this.hasUsedAIDraft = false; // Reset "Bring it Down" button
+ // 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.aiWrittenFeedback = ''; // Reset written feedback for new draft
+ this.hasCopiedAiText = false;
+ this.showUsageCheckboxes = false;
- this.alert.showToast(
- 'success',
- 'AI draft generated successfully',
+ this.alert.showToast(
+ 'success',
+ 'AI draft generated successfully',
'bottom-end',
3000,
false,
@@ -663,13 +746,26 @@ export default class ResponseNewComponent extends Component {
if (!this.canBringDown || !this.aiGeneratedText) {
return;
}
+ // Show usage checkboxes instead of immediately copying
+ this.showUsageCheckboxes = true;
+ }
- // Get current editor content (HTML format)
- const currentText = this.quillText || '';
- const separator = currentText.trim() ? '
' : '';
+ @action
+ continueWithAIDraft() {
+ if (!this.hasSelectedUsageOption) {
+ this.alert.showToast(
+ 'info',
+ 'Please select at least one option before continuing',
+ 'bottom-end',
+ 3000,
+ false,
+ null
+ );
+ return;
+ }
- // Combine HTML
- const newText = currentText + separator + this.aiGeneratedText;
+ // Replace editor content with pure AI text
+ const newText = this.aiGeneratedText;
// Set pending content and force Quill re-render
this.pendingContent = newText;
@@ -684,6 +780,8 @@ export default class ResponseNewComponent extends Component {
// Mark draft as used
this.hasUsedAIDraft = true;
+ this.showUsageCheckboxes = false;
+ this.hasCopiedAiText = true;
this.alert.showToast(
'success',
@@ -694,4 +792,9 @@ export default class ResponseNewComponent extends Component {
null
);
}
+
+ @action
+ setStarRating(rating) {
+ this.aiDraftRating = rating;
+ }
}
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,
diff --git a/app/styles/_response-mentor-thread.scss b/app/styles/_response-mentor-thread.scss
index bcf7ac418..77bc13b41 100644
--- a/app/styles/_response-mentor-thread.scss
+++ b/app/styles/_response-mentor-thread.scss
@@ -205,22 +205,171 @@
cursor: default;
}
}
+
+ // Star Rating and Button Row
+ .ai-draft-actions-wrap {
+ position: relative;
+ }
+
+ .ai-draft-actions {
+ display: flex;
+ align-items: center;
+ gap: 1em;
+
+ &.is-hidden {
+ display: none;
+ }
+ }
+
+ .star-rating-container {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ gap: 0.5em;
+
+ .star-rating-label {
+ font-weight: 500;
+ margin: 0;
+ font-size: 13px;
+ white-space: nowrap;
+
+ .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-feedback-container {
+ flex: 0 1 auto;
+ max-width: 450px;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ gap: 0.5em;
+
+ .ai-feedback-label {
+ font-weight: 500;
+ margin: 0;
+ font-size: 13px;
+ white-space: nowrap;
+
+ .required-asterisk {
+ color: #d9534f;
+ }
+ }
+
+ textarea {
+ height: 30px;
+ padding: 4px 8px;
+ font-size: 13px;
+ }
+ }
+
.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
+ }
+ }
+
+ // Usage Checkboxes
+ .ai-usage-checkboxes {
+ position: absolute !important;
+ left: 50%;
+ transform: translateX(-50%);
+ top: calc(100% - 4px);
+ width: 360px;
+ max-width: 92vw;
+ max-height: 320px;
+ overflow-y: auto;
+ padding: 1em;
+ background-color: #f9f9f9;
+ border-radius: 6px;
+ border: 2px solid #e0e0e0;
+ box-shadow: 0 6px 14px rgba(0, 0, 0, 0.12);
+ z-index: 5;
+
+ .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;
+ }
+ }
+
+ .ai-draft-actions.is-hidden + .ai-usage-checkboxes {
+ top: -10px;
+ }
+
+ @media (max-width: 520px) {
+ .ai-usage-checkboxes {
+ left: 0;
+ transform: none;
+ width: 100%;
+ }
}
}