From 4e336b52a8f5f00ad3aa7d78a7b8c1fa63c31e86 Mon Sep 17 00:00:00 2001 From: Hiroki Terashima Date: Fri, 2 May 2025 15:31:00 -0700 Subject: [PATCH 1/4] feat(AIChat): Send OR response to AI while hiding it from the chat --- .../wise5/components/aiChat/AiChatMessage.ts | 4 ++- .../ai-chat-bot-message.component.spec.ts | 3 ++- .../ai-chat-messages.component.html | 2 +- .../ai-chat-student-message.component.spec.ts | 14 ++++++---- .../ai-chat-student.component.spec.ts | 26 ++++++++++++++----- .../ai-chat-student.component.ts | 17 +++++++++--- 6 files changed, 48 insertions(+), 18 deletions(-) diff --git a/src/assets/wise5/components/aiChat/AiChatMessage.ts b/src/assets/wise5/components/aiChat/AiChatMessage.ts index c1ed8c06cdc..82de4c509d9 100644 --- a/src/assets/wise5/components/aiChat/AiChatMessage.ts +++ b/src/assets/wise5/components/aiChat/AiChatMessage.ts @@ -1,9 +1,11 @@ export class AiChatMessage { content: string; + hidden: boolean; role: 'assistant' | 'system' | 'user'; - constructor(role: 'assistant' | 'system' | 'user', content: string) { + constructor(role: 'assistant' | 'system' | 'user', content: string, hidden: boolean = false) { this.content = content; this.role = role; + this.hidden = hidden; } } diff --git a/src/assets/wise5/components/aiChat/ai-chat-bot-message/ai-chat-bot-message.component.spec.ts b/src/assets/wise5/components/aiChat/ai-chat-bot-message/ai-chat-bot-message.component.spec.ts index 2de9b16d6af..92199cd2a8f 100644 --- a/src/assets/wise5/components/aiChat/ai-chat-bot-message/ai-chat-bot-message.component.spec.ts +++ b/src/assets/wise5/components/aiChat/ai-chat-bot-message/ai-chat-bot-message.component.spec.ts @@ -2,6 +2,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { AiChatBotMessageComponent } from './ai-chat-bot-message.component'; import { MatIconModule } from '@angular/material/icon'; import { ComputerAvatarService } from '../../../services/computerAvatarService'; +import { AiChatMessage } from '../AiChatMessage'; describe('AiChatBotMessageComponent', () => { let component: AiChatBotMessageComponent; @@ -15,7 +16,7 @@ describe('AiChatBotMessageComponent', () => { }); fixture = TestBed.createComponent(AiChatBotMessageComponent); component = fixture.componentInstance; - component.message = { content: 'Hello', role: 'assistant' }; + component.message = new AiChatMessage('assistant', 'Hello'); component.computerAvatar = { id: 'robot1', name: 'Robot', diff --git a/src/assets/wise5/components/aiChat/ai-chat-messages/ai-chat-messages.component.html b/src/assets/wise5/components/aiChat/ai-chat-messages/ai-chat-messages.component.html index 81e8dacce79..2675bbb1622 100644 --- a/src/assets/wise5/components/aiChat/ai-chat-messages/ai-chat-messages.component.html +++ b/src/assets/wise5/components/aiChat/ai-chat-messages/ai-chat-messages.component.html @@ -1,6 +1,6 @@ { let component: AiChatStudentMessageComponent; @@ -10,13 +11,16 @@ describe('AiChatStudentMessageComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - declarations: [AiChatStudentMessageComponent], - imports: [], - providers: [ConfigService, provideHttpClient(withInterceptorsFromDi()), provideHttpClientTesting()] -}); + declarations: [AiChatStudentMessageComponent], + providers: [ + ConfigService, + provideHttpClient(withInterceptorsFromDi()), + provideHttpClientTesting() + ] + }); fixture = TestBed.createComponent(AiChatStudentMessageComponent); component = fixture.componentInstance; - component.message = { content: 'Hello', role: 'user' }; + component.message = new AiChatMessage('user', 'Hello'); fixture.detectChanges(); }); diff --git a/src/assets/wise5/components/aiChat/ai-chat-student/ai-chat-student.component.spec.ts b/src/assets/wise5/components/aiChat/ai-chat-student/ai-chat-student.component.spec.ts index 913813ccb11..7976a48cbd0 100644 --- a/src/assets/wise5/components/aiChat/ai-chat-student/ai-chat-student.component.spec.ts +++ b/src/assets/wise5/components/aiChat/ai-chat-student/ai-chat-student.component.spec.ts @@ -16,6 +16,9 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { MatSnackBarModule } from '@angular/material/snack-bar'; import { ChatInputComponent } from '../../../common/chat-input/chat-input.component'; import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; +import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; +import { MatButtonHarness } from '@angular/material/button/testing'; +import { By } from '@angular/platform-browser'; describe('AiChatStudentComponent', () => { let component: AiChatStudentComponent; @@ -23,8 +26,9 @@ describe('AiChatStudentComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - declarations: [AiChatStudentComponent], - imports: [AiChatModule, + declarations: [AiChatStudentComponent], + imports: [ + AiChatModule, BrowserAnimationsModule, ChatInputComponent, ComponentHeaderComponent, @@ -34,9 +38,14 @@ describe('AiChatStudentComponent', () => { MatFormFieldModule, MatInputModule, MatSnackBarModule, - StudentTeacherCommonServicesModule], - providers: [AiChatService, provideHttpClient(withInterceptorsFromDi()), provideHttpClientTesting()] -}); + StudentTeacherCommonServicesModule + ], + providers: [ + AiChatService, + provideHttpClient(withInterceptorsFromDi()), + provideHttpClientTesting() + ] + }); fixture = TestBed.createComponent(AiChatStudentComponent); component = fixture.componentInstance; component.component = new AiChatComponent({ id: 'component1', type: 'aiChat' }, 'node1'); @@ -45,7 +54,12 @@ describe('AiChatStudentComponent', () => { fixture.detectChanges(); }); - it('should create', () => { + it('should not show response from a connected component', async () => { + component.processConnectedComponentState({ studentData: { response: 'test response' } }); + const loader = TestbedHarnessEnvironment.loader(fixture); + await (await loader.getHarness(MatButtonHarness)).click(); + const messages = fixture.debugElement.queryAll(By.css('a-chat-student-message')); + expect(messages.length).toBe(0); expect(component).toBeTruthy(); }); }); diff --git a/src/assets/wise5/components/aiChat/ai-chat-student/ai-chat-student.component.ts b/src/assets/wise5/components/aiChat/ai-chat-student/ai-chat-student.component.ts index ad16fa77d69..459a6cf5d6b 100644 --- a/src/assets/wise5/components/aiChat/ai-chat-student/ai-chat-student.component.ts +++ b/src/assets/wise5/components/aiChat/ai-chat-student/ai-chat-student.component.ts @@ -19,15 +19,16 @@ import { ComputerAvatarService } from '../../../services/computerAvatarService'; import { StudentStatusService } from '../../../services/studentStatusService'; @Component({ - selector: 'ai-chat-student', - templateUrl: './ai-chat-student.component.html', - styleUrls: ['./ai-chat-student.component.scss'], - standalone: false + selector: 'ai-chat-student', + templateUrl: './ai-chat-student.component.html', + styleUrls: ['./ai-chat-student.component.scss'], + standalone: false }) export class AiChatStudentComponent extends ComponentStudent { component: AiChatComponent; protected computerAvatar: ComputerAvatar; protected computerAvatarSelectorVisible: boolean = false; + private connectedComponentResponse: string; protected messages: AiChatMessage[] = []; protected studentResponse: string = ''; protected submitEnabled: boolean = false; @@ -82,6 +83,10 @@ export class AiChatStudentComponent extends ComponentStudent { protected async submitStudentResponse(response: string): Promise { this.waitingForComputerResponse = true; + if (this.connectedComponentResponse != null) { + this.messages.push(new AiChatMessage('user', this.connectedComponentResponse, true)); + this.connectedComponentResponse = null; + } this.messages.push(new AiChatMessage('user', response)); try { const response = await this.aiChatService.sendChatMessage( @@ -119,6 +124,10 @@ export class AiChatStudentComponent extends ComponentStudent { return promise; } + processConnectedComponentState(componentState: any): void { + this.connectedComponentResponse = componentState.studentData.response; + } + initializeComputerAvatar: () => void; } From 75e5e94e11dcef4a9463f877214bd4f9c6561270 Mon Sep 17 00:00:00 2001 From: Hiroki Terashima Date: Wed, 7 May 2025 14:01:22 -0700 Subject: [PATCH 2/4] Add ConnectedComponentAuthoring to AiChat advanced view --- .../edit-ai-chat-advanced.component.html | 8 ++++++++ .../edit-ai-chat-advanced.component.spec.ts | 17 ++++++++++++----- .../edit-ai-chat-advanced.component.ts | 7 ++++--- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/assets/wise5/components/aiChat/edit-ai-chat-advanced/edit-ai-chat-advanced.component.html b/src/assets/wise5/components/aiChat/edit-ai-chat-advanced/edit-ai-chat-advanced.component.html index 51248c42cca..caab9450c84 100644 --- a/src/assets/wise5/components/aiChat/edit-ai-chat-advanced/edit-ai-chat-advanced.component.html +++ b/src/assets/wise5/components/aiChat/edit-ai-chat-advanced/edit-ai-chat-advanced.component.html @@ -4,4 +4,12 @@ {{ model }} + diff --git a/src/assets/wise5/components/aiChat/edit-ai-chat-advanced/edit-ai-chat-advanced.component.spec.ts b/src/assets/wise5/components/aiChat/edit-ai-chat-advanced/edit-ai-chat-advanced.component.spec.ts index e7373d145d8..b94659becb6 100644 --- a/src/assets/wise5/components/aiChat/edit-ai-chat-advanced/edit-ai-chat-advanced.component.spec.ts +++ b/src/assets/wise5/components/aiChat/edit-ai-chat-advanced/edit-ai-chat-advanced.component.spec.ts @@ -15,13 +15,20 @@ describe('EditAiChatAdvancedComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - declarations: [EditAiChatAdvancedComponent], - imports: [BrowserAnimationsModule, + declarations: [EditAiChatAdvancedComponent], + imports: [ + BrowserAnimationsModule, ComponentAuthoringModule, MatDialogModule, - StudentTeacherCommonServicesModule], - providers: [TeacherNodeService, TeacherProjectService, provideHttpClient(withInterceptorsFromDi()), provideHttpClientTesting()] -}); + StudentTeacherCommonServicesModule + ], + providers: [ + TeacherNodeService, + TeacherProjectService, + provideHttpClient(withInterceptorsFromDi()), + provideHttpClientTesting() + ] + }); fixture = TestBed.createComponent(EditAiChatAdvancedComponent); component = fixture.componentInstance; component.nodeId = 'node1'; diff --git a/src/assets/wise5/components/aiChat/edit-ai-chat-advanced/edit-ai-chat-advanced.component.ts b/src/assets/wise5/components/aiChat/edit-ai-chat-advanced/edit-ai-chat-advanced.component.ts index 0e021d179d1..b44298e1265 100644 --- a/src/assets/wise5/components/aiChat/edit-ai-chat-advanced/edit-ai-chat-advanced.component.ts +++ b/src/assets/wise5/components/aiChat/edit-ai-chat-advanced/edit-ai-chat-advanced.component.ts @@ -3,11 +3,12 @@ import { EditAdvancedComponentComponent } from '../../../../../app/authoring-too import { AiChatContent } from '../AiChatContent'; @Component({ - selector: 'edit-ai-chat-advanced', - templateUrl: './edit-ai-chat-advanced.component.html', - standalone: false + selector: 'edit-ai-chat-advanced', + templateUrl: './edit-ai-chat-advanced.component.html', + standalone: false }) export class EditAiChatAdvancedComponent extends EditAdvancedComponentComponent { + protected allowedConnectedComponentTypes = ['OpenResponse']; componentContent: AiChatContent; protected models: string[] = ['gpt-3.5-turbo', 'gpt-4']; } From b73a5363179e4c30cdc0be74cbc17a1e46a1f30b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 7 May 2025 21:11:21 +0000 Subject: [PATCH 3/4] Updated messages --- src/messages.xlf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/messages.xlf b/src/messages.xlf index cdf6e7c8d46..2ddf657b030 100644 --- a/src/messages.xlf +++ b/src/messages.xlf @@ -15795,7 +15795,7 @@ Are you sure you want to proceed? An error occurred. src/assets/wise5/components/aiChat/ai-chat-student/ai-chat-student.component.ts - 96 + 101 From b8c5b594e453b4c7f616963acc84c29ad9a2e964 Mon Sep 17 00:00:00 2001 From: Hiroki Terashima Date: Wed, 28 May 2025 09:11:11 -0700 Subject: [PATCH 4/4] Fix test to ensure that no new message was added --- .../ai-chat-student/ai-chat-student.component.spec.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/assets/wise5/components/aiChat/ai-chat-student/ai-chat-student.component.spec.ts b/src/assets/wise5/components/aiChat/ai-chat-student/ai-chat-student.component.spec.ts index 7976a48cbd0..458a313d5c2 100644 --- a/src/assets/wise5/components/aiChat/ai-chat-student/ai-chat-student.component.spec.ts +++ b/src/assets/wise5/components/aiChat/ai-chat-student/ai-chat-student.component.spec.ts @@ -49,17 +49,18 @@ describe('AiChatStudentComponent', () => { fixture = TestBed.createComponent(AiChatStudentComponent); component = fixture.componentInstance; component.component = new AiChatComponent({ id: 'component1', type: 'aiChat' }, 'node1'); + component.componentState = { studentData: { messages: [{ role: 'user' }] } }; spyOn(component, 'isNotebookEnabled').and.returnValue(false); spyOn(TestBed.inject(ProjectService), 'getThemeSettings').and.returnValue({}); fixture.detectChanges(); }); it('should not show response from a connected component', async () => { + const studentMessages = fixture.debugElement.queryAll(By.css('ai-chat-student-message')); + expect(studentMessages.length).toBe(1); component.processConnectedComponentState({ studentData: { response: 'test response' } }); const loader = TestbedHarnessEnvironment.loader(fixture); await (await loader.getHarness(MatButtonHarness)).click(); - const messages = fixture.debugElement.queryAll(By.css('a-chat-student-message')); - expect(messages.length).toBe(0); - expect(component).toBeTruthy(); + expect(studentMessages.length).toBe(1); // no new message should be added }); });