Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/components/response-mentor-reply.js
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ export default class ResponseMentorReplyComponent extends Component {
try {
const draft = await this.aiDraft.generateDraft(
this.args.submission.id,
'A',
'D',
this.args.workspace?.id
);
this.aiGeneratedText = draft;
Expand Down
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>
123 changes: 114 additions & 9 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 @@ -624,7 +703,7 @@ export default class ResponseNewComponent extends Component {
try {
const draft = await this.aiDraft.generateDraft(
submissionId,
'A',
'D',
this.args.workspace?.id
);

Expand All @@ -634,6 +713,10 @@ 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.aiWrittenFeedback = ''; // Reset written feedback for new draft
this.hasCopiedAiText = false;
this.showUsageCheckboxes = false;

this.alert.showToast(
'success',
Expand Down Expand Up @@ -667,12 +750,27 @@ 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
// Preserve existing editor content and append AI text
const currentText = this.quillText || '';
const separator = currentText.trim() ? '<p><br></p>' : '';
const newText = currentText + separator + this.aiGeneratedText;

// Set pending content and force Quill re-render
Expand All @@ -688,6 +786,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 @@ -698,4 +798,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