Skip to content
Open
72 changes: 63 additions & 9 deletions app/components/response-new.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,66 @@
/>
</div>

<button
type='button'
class='ai-bring-down-btn'
disabled={{not this.canBringDown}}
{{on 'click' this.bringAiDraftDown}}
>
Bring it Down
</button>
{{! Star Rating and Button Row }}
<div class='ai-draft-actions-wrap'>
<div class='ai-draft-actions {{if this.showUsageCheckboxes "is-hidden"}}'>
{{#if this.showRatingControls}}
<div class='star-rating-container'>
<label class='star-rating-label'>Rate AI Draft <span class='required-asterisk'>*</span></label>
<div class='star-rating-stars'>
{{#each this.starDefinitions as |star|}}
<i
class='fas fa-star star-icon {{if (this.isStarFilled star.value) "star-filled" "star-empty"}}'
title={{star.tooltip}}
{{on 'click' (fn this.setStarRating star.value)}}
role='button'
tabindex='0'
></i>
{{/each}}
</div>
</div>

<div class='ai-feedback-container'>
<label for='ai-written-feedback' class='ai-feedback-label'>Written Feedback <span class='required-asterisk'>*</span></label>
<Textarea
id='ai-written-feedback'
placeholder='Share your thoughts on this AI-generated draft...'
rows={{2}}
@value={{this.aiWrittenFeedback}}
{{on 'input' this.updateAiWrittenFeedback}}
/>
</div>

<button
type='button'
class='ai-bring-down-btn'
disabled={{not this.canBringDown}}
{{on 'click' this.bringAiDraftDown}}
>
Bring it Down
</button>
{{/if}}
</div>
{{! Usage Checkboxes }}
{{#if this.showUsageCheckboxes}}
<div class='ai-usage-checkboxes'>
<p class='usage-prompt'>How will you use this AI draft? <span class='required-asterisk'>*</span> (Check all that apply)</p>
{{#each this.usageOptions as |option|}}
<label class='usage-option'>
<Input @type='checkbox' @checked={{get this option.key}} />
{{option.label}}
</label>
{{/each}}
<button
type='button'
class='ai-continue-btn primary-button'
{{on 'click' this.continueWithAIDraft}}
>
Continue
</button>
</div>
{{/if}}
</div>
</div>
{{/if}}

Expand Down Expand Up @@ -156,6 +208,8 @@
{{/if}}
</div>

{{!-- AI action section removed temporarily; will be restored on a dedicated branch --}}

{{#if this.doShowLoadingMessage}}
<LoadingElem />
{{/if}}
Expand All @@ -179,4 +233,4 @@
>Save as Draft</button>
</div>
</section>
</div>
</div>
133 changes: 118 additions & 15 deletions app/components/response-new.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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() {
Expand All @@ -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) {
Expand Down Expand Up @@ -413,6 +491,7 @@ export default class ResponseNewComponent extends Component {
this[p] = !this[p];
}

@action
cleanupTrashedItems(response) {
response.selections?.forEach((selection) => {
if (selection.isTrashed) {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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() ? '<p><br></p><p><br></p>' : '';
@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;
Expand All @@ -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',
Expand All @@ -694,4 +792,9 @@ export default class ResponseNewComponent extends Component {
null
);
}

@action
setStarRating(rating) {
this.aiDraftRating = rating;
}
}
7 changes: 7 additions & 0 deletions app/routes/responses/new/submission.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading