diff --git a/src/assets/wise5/classroomMonitor/classroomMonitorComponents/component-summary/component-summary.component.html b/src/assets/wise5/classroomMonitor/classroomMonitorComponents/component-summary/component-summary.component.html
index f7c1a5d0092..02c58d4a888 100644
--- a/src/assets/wise5/classroomMonitor/classroomMonitorComponents/component-summary/component-summary.component.html
+++ b/src/assets/wise5/classroomMonitor/classroomMonitorComponents/component-summary/component-summary.component.html
@@ -84,6 +84,18 @@
/>
+ } @else if (component?.type === 'Discussion') {
+
+
+
+
+
}
}
diff --git a/src/assets/wise5/classroomMonitor/classroomMonitorComponents/component-summary/component-summary.component.ts b/src/assets/wise5/classroomMonitor/classroomMonitorComponents/component-summary/component-summary.component.ts
index 7c8d98686f8..81d4190fd44 100644
--- a/src/assets/wise5/classroomMonitor/classroomMonitorComponents/component-summary/component-summary.component.ts
+++ b/src/assets/wise5/classroomMonitor/classroomMonitorComponents/component-summary/component-summary.component.ts
@@ -16,10 +16,12 @@ import { MatCardModule } from '@angular/material/card';
import { CRaterService } from '../../../services/cRaterService';
import { OpenResponseSummaryDisplayComponent } from '../../../directives/teacher-summary-display/open-response-summary-display/open-response-summary-display.component';
import { ProjectService } from '../../../services/projectService';
+import { DiscussionSummaryDisplayComponent } from '../../../directives/teacher-summary-display/discussion-summary-display/discussion-summary-display.component';
@Component({
imports: [
ComponentCompletionComponent,
+ DiscussionSummaryDisplayComponent,
IdeasSummaryComponent,
MatCardModule,
MatchSummaryDisplayComponent,
@@ -83,7 +85,7 @@ export class ComponentSummaryComponent {
(this.hasScoresSummary && this.hasScoreAnnotation) ||
this.hasIdeaRubricData ||
this.component?.type === 'Match';
- if (this.component?.type === 'OpenResponse') {
+ if (this.component?.type === 'OpenResponse' || this.component?.type === 'Discussion') {
this.hasSummaryData = this.projectService.getProject().ai?.enabled;
}
}
diff --git a/src/assets/wise5/directives/teacher-summary-display/ai-summary-display/ai-summary-display.component.html b/src/assets/wise5/directives/teacher-summary-display/ai-summary-display/ai-summary-display.component.html
new file mode 100644
index 00000000000..f4bda35f709
--- /dev/null
+++ b/src/assets/wise5/directives/teacher-summary-display/ai-summary-display/ai-summary-display.component.html
@@ -0,0 +1,24 @@
+@if (hasStudentResponses) {
+
+
+
+ @if (generatingSummary) {
+
+ }
+
+ @if (newSummaryAvailable) {
+
*New responses since last summary
+ }
+
+ @if (summary) {
+
+
+ Summary generated {{ summaryDate | date: 'short' }} from
+ {{ getLatestPeriodComponentStates().length }} responses
+
+ }
+} @else {
+ No student responses
+}
diff --git a/src/assets/wise5/directives/teacher-summary-display/ai-summary-display/ai-summary-display.component.ts b/src/assets/wise5/directives/teacher-summary-display/ai-summary-display/ai-summary-display.component.ts
new file mode 100644
index 00000000000..db65ccbc4ff
--- /dev/null
+++ b/src/assets/wise5/directives/teacher-summary-display/ai-summary-display/ai-summary-display.component.ts
@@ -0,0 +1,78 @@
+import { Component, inject } from '@angular/core';
+import { TeacherSummaryDisplayComponent } from '../teacher-summary-display.component';
+import { MatButton } from '@angular/material/button';
+import { MatIcon } from '@angular/material/icon';
+import { AwsBedRockService } from '../../../../../app/chatbot/awsBedRock.service';
+import { ChatMessage } from '../../../../../app/chatbot/chat';
+import { TeacherDataService } from '../../../services/teacherDataService';
+import { MatProgressSpinner } from '@angular/material/progress-spinner';
+import { LocalStorageService } from '../../../../../app/services/localStorageService';
+import { MarkdownComponent } from 'ngx-markdown';
+import { DatePipe } from '@angular/common';
+
+@Component({
+ imports: [DatePipe, MarkdownComponent, MatButton, MatIcon, MatProgressSpinner],
+ templateUrl: './ai-summary-display.component.html'
+})
+export abstract class AiSummaryDisplayComponent extends TeacherSummaryDisplayComponent {
+ protected awsBedRockService: AwsBedRockService = inject(AwsBedRockService);
+ protected generatingSummary: boolean = false;
+ protected hasStudentResponses: boolean = false;
+ private localStorageService: LocalStorageService = inject(LocalStorageService);
+ protected newSummaryAvailable: boolean = false;
+ protected summary: string;
+ private summaryTimestamp: number;
+
+ ngOnInit(): void {
+ this.renderDisplay();
+ }
+
+ protected renderDisplay(): void {
+ super.renderDisplay();
+ const latestPeriodComponentStates = this.getLatestPeriodComponentStates();
+ this.hasStudentResponses = latestPeriodComponentStates.length > 0;
+ if (!this.hasStudentResponses) {
+ return;
+ }
+ this.summary = this.localStorageService.getItem(this.getSummaryKey()) || '';
+ this.summaryTimestamp = this.localStorageService.getItem(this.getSummaryTimestampKey()) || 0;
+ const lastResponseTime = latestPeriodComponentStates.reduce(
+ (max, state) => Math.max(max, state.serverSaveTime),
+ 0
+ );
+ this.newSummaryAvailable =
+ this.summaryTimestamp > 0 && lastResponseTime > this.summaryTimestamp;
+ }
+
+ protected getLatestPeriodComponentStates(): any[] {
+ return (this.dataService as TeacherDataService)
+ .getComponentStatesByComponentId(this.componentId)
+ .filter((state) => state.periodId === this.periodId || this.periodId === -1)
+ .sort((a, b) => a.serverSaveTime - b.serverSaveTime);
+ }
+
+ protected async generateSummary(): Promise {
+ this.generatingSummary = true;
+ const prompt = this.projectService.getComponent(this.nodeId, this.componentId).prompt;
+ this.summary = await this.awsBedRockService.sendMessage([
+ new ChatMessage('system', this.getSystemPrompt(prompt), this.nodeId),
+ new ChatMessage('user', this.getStudentResponses(), this.nodeId)
+ ]);
+ this.localStorageService.setItem(this.getSummaryKey(), this.summary);
+ this.localStorageService.setItem(this.getSummaryTimestampKey(), new Date().getTime());
+ this.generatingSummary = false;
+ this.newSummaryAvailable = false;
+ }
+
+ protected abstract getStudentResponses(): string;
+
+ protected abstract getSystemPrompt(prompt: string): string;
+
+ private getSummaryKey(): string {
+ return `component-summary-${this.periodId}-${this.nodeId}-${this.componentId}`;
+ }
+
+ private getSummaryTimestampKey(): string {
+ return `component-summary-timestamp-${this.periodId}-${this.nodeId}-${this.componentId}`;
+ }
+}
diff --git a/src/assets/wise5/directives/teacher-summary-display/discussion-summary-display/discussion-summary-display.component.ts b/src/assets/wise5/directives/teacher-summary-display/discussion-summary-display/discussion-summary-display.component.ts
new file mode 100644
index 00000000000..b701a0c131a
--- /dev/null
+++ b/src/assets/wise5/directives/teacher-summary-display/discussion-summary-display/discussion-summary-display.component.ts
@@ -0,0 +1,49 @@
+import { Component } from '@angular/core';
+import { MatButton } from '@angular/material/button';
+import { MatIcon } from '@angular/material/icon';
+import { MatProgressSpinner } from '@angular/material/progress-spinner';
+import { MarkdownComponent } from 'ngx-markdown';
+import { AiSummaryDisplayComponent } from '../ai-summary-display/ai-summary-display.component';
+import { DatePipe } from '@angular/common';
+
+interface Thread {
+ id: number;
+ post: string;
+ replies: string[];
+}
+
+@Component({
+ imports: [DatePipe, MarkdownComponent, MatButton, MatIcon, MatProgressSpinner],
+ selector: 'discussion-summary-display',
+ templateUrl: '../ai-summary-display/ai-summary-display.component.html'
+})
+export class DiscussionSummaryDisplayComponent extends AiSummaryDisplayComponent {
+ protected getSystemPrompt(prompt: string): string {
+ return `You are a teacher who is summarizing students' discussion threads, which include posts and replies to the following question: "${prompt}".
+ Each thread is in the format: PostReply 1Reply 2.
+ In the same language as the question, provide a summary of the threads in 100 words or less.`;
+ }
+
+ protected getStudentResponses(): string {
+ return this.getDiscussionThreads().reduce(
+ (soFar, thread) =>
+ `${soFar}${thread.post}${thread.replies.map((reply) => `${reply}`).join('')}`,
+ ''
+ );
+ }
+
+ private getDiscussionThreads(): Thread[] {
+ const states = this.getLatestPeriodComponentStates();
+ const threads = states
+ .filter((state) => state.studentData.componentStateIdReplyingTo == null)
+ .map((post) => ({ id: post.id, post: post.studentData.response, replies: [] }));
+ states
+ .filter((state) => state.studentData.componentStateIdReplyingTo != null)
+ .forEach((reply) => {
+ threads
+ .find((t) => t.id === reply.studentData.componentStateIdReplyingTo)
+ ?.replies.push(reply.studentData.response);
+ });
+ return threads;
+ }
+}
diff --git a/src/messages.xlf b/src/messages.xlf
index f0bae20cbf5..89c84a09e92 100644
--- a/src/messages.xlf
+++ b/src/messages.xlf
@@ -21846,6 +21846,66 @@ If this problem continues, let your teacher know and move on to the next activit
401
+
+ Generate Class Summary
+
+ src/assets/wise5/directives/teacher-summary-display/ai-summary-display/ai-summary-display.component.html
+ 5,7
+
+
+ src/assets/wise5/directives/teacher-summary-display/ai-summary-display/ai-summary-display.component.html
+ 5,7
+
+
+ src/assets/wise5/directives/teacher-summary-display/open-response-summary-display/open-response-summary-display.component.html
+ 5,7
+
+
+
+ *New responses since last summary
+
+ src/assets/wise5/directives/teacher-summary-display/ai-summary-display/ai-summary-display.component.html
+ 12,16
+
+
+ src/assets/wise5/directives/teacher-summary-display/ai-summary-display/ai-summary-display.component.html
+ 12,16
+
+
+ src/assets/wise5/directives/teacher-summary-display/open-response-summary-display/open-response-summary-display.component.html
+ 12,16
+
+
+
+ Summary generated from responses
+
+ src/assets/wise5/directives/teacher-summary-display/ai-summary-display/ai-summary-display.component.html
+ 18,22
+
+
+ src/assets/wise5/directives/teacher-summary-display/ai-summary-display/ai-summary-display.component.html
+ 18,22
+
+
+ src/assets/wise5/directives/teacher-summary-display/open-response-summary-display/open-response-summary-display.component.html
+ 18,22
+
+
+
+ No student responses
+
+ src/assets/wise5/directives/teacher-summary-display/ai-summary-display/ai-summary-display.component.html
+ 23,25
+
+
+ src/assets/wise5/directives/teacher-summary-display/ai-summary-display/ai-summary-display.component.html
+ 23,25
+
+
+ src/assets/wise5/directives/teacher-summary-display/open-response-summary-display/open-response-summary-display.component.html
+ 23,25
+
+
Student Ideas Detected
@@ -21930,34 +21990,6 @@ If this problem continues, let your teacher know and move on to the next activit
58,61
-
- Generate Class Summary
-
- src/assets/wise5/directives/teacher-summary-display/open-response-summary-display/open-response-summary-display.component.html
- 5,7
-
-
-
- *New responses since last summary
-
- src/assets/wise5/directives/teacher-summary-display/open-response-summary-display/open-response-summary-display.component.html
- 12,16
-
-
-
- Summary generated from responses
-
- src/assets/wise5/directives/teacher-summary-display/open-response-summary-display/open-response-summary-display.component.html
- 18,22
-
-
-
- No student responses
-
- src/assets/wise5/directives/teacher-summary-display/open-response-summary-display/open-response-summary-display.component.html
- 23,25
-
-
The student will see a graph of their individual data here.