Skip to content

Commit

Permalink
feat(AI Chat): Implement AI Chat item (#1741)
Browse files Browse the repository at this point in the history
Co-authored-by: Jonathan Lim-Breitbart <[email protected]>
  • Loading branch information
geoffreykwan and breity authored Apr 17, 2024
1 parent 65ebbea commit 6348a59
Show file tree
Hide file tree
Showing 88 changed files with 1,578 additions and 315 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ <h2 mat-dialog-title i18n>Advanced Settings</h2>
<mat-divider></mat-divider>
<div mat-dialog-content>
<ng-container [ngSwitch]="component.content.type">
<edit-ai-chat-advanced
*ngSwitchCase="'AiChat'"
[nodeId]="component.nodeId"
[componentId]="component.id"
></edit-ai-chat-advanced>
<edit-animation-advanced
*ngSwitchCase="'Animation'"
[nodeId]="component.nodeId"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { TeacherProjectService } from '../../../assets/wise5/services/teacherPro
import { VLEProjectService } from '../../../assets/wise5/vle/vleProjectService';
import { ShowNodeInfoDialogComponent } from './show-node-info-dialog.component';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { ComponentTypeServiceModule } from '../../../assets/wise5/services/componentTypeService.module';

let component: ShowNodeInfoDialogComponent;
const componentRubric: string = 'This is the component rubric.';
Expand Down Expand Up @@ -52,6 +53,7 @@ describe('ShowNodeInfoDialogComponent', () => {
],
imports: [
ClassroomMonitorTestingModule,
ComponentTypeServiceModule,
MatCardModule,
MatDialogModule,
MatIconModule,
Expand Down
20 changes: 18 additions & 2 deletions src/app/services/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,27 @@ export class UserService {
}

isStudent(): boolean {
return this.isAuthenticated && this.getRoles().includes('student');
return this.isRole('student');
}

isTeacher(): boolean {
return this.isAuthenticated && this.getRoles().includes('teacher');
return this.isRole('teacher');
}

isTrustedAuthor(): boolean {
return this.isRole('trustedAuthor');
}

isResearcher(): boolean {
return this.isRole('researcher');
}

isAdmin(): boolean {
return this.isRole('admin');
}

private isRole(role: string): boolean {
return this.isAuthenticated && this.getRoles().includes(role);
}

getRoles(): string[] {
Expand Down
2 changes: 2 additions & 0 deletions src/app/student-teacher-common-services.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,12 @@ import { PeerGroupService } from '../assets/wise5/services/peerGroupService';
import { NodeProgressService } from '../assets/wise5/services/nodeProgressService';
import { CompletionService } from '../assets/wise5/services/completionService';
import { StudentNodeService } from '../assets/wise5/services/studentNodeService';
import { AiChatService } from '../assets/wise5/components/aiChat/aiChatService';

@NgModule({
providers: [
AchievementService,
AiChatService,
AnimationService,
AnnotationService,
AudioOscillatorService,
Expand Down
7 changes: 6 additions & 1 deletion src/app/teacher/component-authoring.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,12 @@ import { ComponentAuthoringComponent } from '../../assets/wise5/authoringTool/co
import { WiseTinymceEditorModule } from '../../assets/wise5/directives/wise-tinymce-editor/wise-tinymce-editor.module';
import { WiseLinkAuthoringDialogComponent } from '../../assets/wise5/authoringTool/wise-link-authoring-dialog/wise-link-authoring-dialog.component';
import { EditComponentAdvancedButtonComponent } from '../../assets/wise5/authoringTool/components/edit-component-advanced-button/edit-component-advanced-button.component';
import { AiChatAuthoringComponent } from '../../assets/wise5/components/aiChat/ai-chat-authoring/ai-chat-authoring.component';
import { EditAiChatAdvancedComponent } from '../../assets/wise5/components/aiChat/edit-ai-chat-advanced/edit-ai-chat-advanced.component';

@NgModule({
declarations: [
AiChatAuthoringComponent,
AnimationAuthoring,
AudioOscillatorAuthoring,
AuthorUrlParametersComponent,
Expand All @@ -97,6 +100,7 @@ import { EditComponentAdvancedButtonComponent } from '../../assets/wise5/authori
DrawAuthoring,
DialogGuidanceAuthoringComponent,
DiscussionAuthoring,
EditAiChatAdvancedComponent,
EditAnimationAdvancedComponent,
EditAudioOscillatorAdvancedComponent,
EditCommonAdvancedComponent,
Expand Down Expand Up @@ -176,13 +180,14 @@ import { EditComponentAdvancedButtonComponent } from '../../assets/wise5/authori
WiseTinymceEditorModule
],
exports: [
AnimationAuthoring,
AiChatAuthoringComponent,
AudioOscillatorAuthoring,
ComponentAuthoringComponent,
ConceptMapAuthoring,
DialogGuidanceAuthoringComponent,
DiscussionAuthoring,
DrawAuthoring,
EditAiChatAdvancedComponent,
EditAnimationAdvancedComponent,
EditAudioOscillatorAdvancedComponent,
EditCommonAdvancedComponent,
Expand Down
3 changes: 3 additions & 0 deletions src/app/teacher/component-grading.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ import { ShowGroupWorkGradingModule } from '../../assets/wise5/components/showGr
import { TableGradingModule } from '../../assets/wise5/components/table/table-grading/table-grading.module';
import { ComponentGradingComponent } from '../../assets/wise5/classroomMonitor/classroomMonitorComponents/component-grading.component';
import { ShowMyWorkGradingModule } from '../../assets/wise5/components/showMyWork/show-my-work-grading/show-my-work-grading.module';
import { AiChatGradingModule } from '../../assets/wise5/components/aiChat/ai-chat-grading/ai-chat-grading.module';

@NgModule({
declarations: [ComponentGradingComponent],
imports: [
AiChatGradingModule,
AnimationGradingModule,
AudioOscillatorGradingModule,
ComponentStateInfoModule,
Expand All @@ -40,6 +42,7 @@ import { ShowMyWorkGradingModule } from '../../assets/wise5/components/showMyWor
TableGradingModule
],
exports: [
AiChatGradingModule,
AnimationGradingModule,
AudioOscillatorGradingModule,
ComponentGradingComponent,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { OutsideUrlInfo } from '../../../components/outsideURL/OutsideUrlInfo';
import { OpenResponseInfo } from '../../../components/openResponse/OpenResponseInfo';
import { ComponentInfo } from '../../../components/ComponentInfo';
import { MatCardModule } from '@angular/material/card';
import { ComponentTypeServiceModule } from '../../../services/componentTypeService.module';

let component: ComponentInfoDialogComponent;
let fixture: ComponentFixture<ComponentInfoDialogComponent>;
Expand All @@ -39,6 +40,7 @@ describe('ComponentInfoDialogComponent', () => {
],
imports: [
BrowserAnimationsModule,
ComponentTypeServiceModule,
HttpClientTestingModule,
MatButtonModule,
MatCardModule,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,23 @@ import { MatSelectModule } from '@angular/material/select';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ComponentTypeSelectorHarness } from './component-type-selector.harness';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { ComponentTypeServiceModule } from '../../../services/componentTypeService.module';
import { UserService } from '../../../../../app/services/user.service';
import { ConfigService } from '../../../services/configService';

let component: ComponentTypeSelectorComponent;
let componentTypeSelectorHarness: ComponentTypeSelectorHarness;
let configService: ConfigService;
let fixture: ComponentFixture<ComponentTypeSelectorComponent>;
let userService: UserService;

describe('ComponentTypeSelectorComponent', () => {
beforeEach(async () => {
TestBed.configureTestingModule({
declarations: [ComponentTypeSelectorComponent],
imports: [
BrowserAnimationsModule,
ComponentTypeServiceModule,
HttpClientTestingModule,
MatFormFieldModule,
MatIconModule,
Expand All @@ -28,6 +34,11 @@ describe('ComponentTypeSelectorComponent', () => {
providers: []
});
fixture = TestBed.createComponent(ComponentTypeSelectorComponent);
configService = TestBed.inject(ConfigService);
spyOn(configService, 'getConfigParam').and.returnValue(true);
userService = TestBed.inject(UserService);
userService.isAuthenticated = true;
spyOn(userService, 'getRoles').and.returnValue(['researcher', 'teacher']);
component = fixture.componentInstance;
component.componentType = 'OpenResponse';
fixture.detectChanges();
Expand Down Expand Up @@ -65,9 +76,9 @@ function selectComponent() {
describe('select first component type', () => {
it('changes to the first component type and the previous button becomes disabled', async () => {
await (await componentTypeSelectorHarness.getComponentTypeSelect()).clickOptions({
text: 'Animation'
text: 'AI Chat'
});
expect(component.componentType).toEqual('Animation');
expect(component.componentType).toEqual('AiChat');
expect(
await (await componentTypeSelectorHarness.getPreviousComponentTypeButton()).isDisabled()
).toBeTrue();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { TeacherNodeService } from '../../../services/teacherNodeService';
import { EditNodeTitleComponent } from '../edit-node-title/edit-node-title.component';
import { AddComponentButtonComponent } from '../add-component-button/add-component-button.component';
import { CopyComponentButtonComponent } from '../copy-component-button/copy-component-button.component';
import { ComponentTypeServiceModule } from '../../../services/componentTypeService.module';

let component: NodeAuthoringComponent;
let component1: any;
Expand All @@ -50,6 +51,7 @@ describe('NodeAuthoringComponent', () => {
imports: [
BrowserAnimationsModule,
ComponentAuthoringModule,
ComponentTypeServiceModule,
DragDropModule,
FormsModule,
HttpClientTestingModule,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { CommonModule } from '@angular/common';
import { NodeGradingViewComponentTestHelper } from '../../nodeGrading/node-grading-view/node-grading-view.component.test.helper';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { of } from 'rxjs';
import { ComponentTypeServiceModule } from '../../../../services/componentTypeService.module';

let component: NodeGradingViewComponent;
let fixture: ComponentFixture<NodeGradingViewComponent>;
Expand All @@ -36,6 +37,7 @@ describe('NodeGradingViewComponent', () => {
BrowserAnimationsModule,
ClassroomMonitorTestingModule,
CommonModule,
ComponentTypeServiceModule,
FlexLayoutModule,
FormsModule,
MatAutocompleteModule,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ClassroomMonitorTestingModule } from '../../../classroom-monitor-testin
import { WorkgroupItemComponent } from './workgroup-item.component';
import { TeacherProjectService } from '../../../../services/teacherProjectService';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { ComponentTypeServiceModule } from '../../../../services/componentTypeService.module';

let component: WorkgroupItemComponent;
let fixture: ComponentFixture<WorkgroupItemComponent>;
Expand All @@ -13,7 +14,7 @@ describe('WorkgroupItemComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [WorkgroupItemComponent],
imports: [ClassroomMonitorTestingModule],
imports: [ClassroomMonitorTestingModule, ComponentTypeServiceModule],
providers: [TeacherProjectService],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
Expand Down
5 changes: 4 additions & 1 deletion src/assets/wise5/common/ComponentFactory.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { AiChatComponent } from '../components/aiChat/AiChatComponent';
import { DialogGuidanceComponent } from '../components/dialogGuidance/DialogGuidanceComponent';
import { MultipleChoiceComponent } from '../components/multipleChoice/MultipleChoiceComponent';
import { PeerChatComponent } from '../components/peerChat/PeerChatComponent';
Expand All @@ -7,7 +8,9 @@ import { ComponentContent } from './ComponentContent';

export class ComponentFactory {
getComponent(content: ComponentContent, nodeId: string): Component {
if (content.type === 'DialogGuidance') {
if (content.type === 'AiChat') {
return new AiChatComponent(content, nodeId);
} else if (content.type === 'DialogGuidance') {
return new DialogGuidanceComponent(content, nodeId);
} else if (content.type === 'MultipleChoice') {
return new MultipleChoiceComponent(content, nodeId);
Expand Down
11 changes: 11 additions & 0 deletions src/assets/wise5/common/apply-mixins.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export function applyMixins(derivedCtor: any, constructors: any[]) {
constructors.forEach((baseCtor) => {
Object.getOwnPropertyNames(baseCtor.prototype).forEach((name) => {
Object.defineProperty(
derivedCtor.prototype,
name,
Object.getOwnPropertyDescriptor(baseCtor.prototype, name) || Object.create(null)
);
});
});
}
21 changes: 21 additions & 0 deletions src/assets/wise5/common/chat-input/chat-input.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<mat-form-field fxFlex class="form-field-no-label form-field-no-hint" appearance="fill">
<textarea
matInput
cdkTextareaAutosize
placeholder="Add response..."
i18n-placeholder
[(ngModel)]="response"
(keypress)="keyPressed($event)"
></textarea>
</mat-form-field>
<button
mat-flat-button
color="primary"
(click)="submit()"
[disabled]="response.length === 0 || submitDisabled"
i18n
>
Send
</button>
</div>
Empty file.
21 changes: 21 additions & 0 deletions src/assets/wise5/common/chat-input/chat-input.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ChatInputComponent } from './chat-input.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

describe('ChatInputComponent', () => {
let component: ChatInputComponent;
let fixture: ComponentFixture<ChatInputComponent>;

beforeEach(() => {
TestBed.configureTestingModule({
imports: [BrowserAnimationsModule, ChatInputComponent]
});
fixture = TestBed.createComponent(ChatInputComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
33 changes: 33 additions & 0 deletions src/assets/wise5/common/chat-input/chat-input.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { FlexLayoutModule } from '@angular/flex-layout';
import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';

@Component({
selector: 'chat-input',
templateUrl: './chat-input.component.html',
styleUrls: ['./chat-input.component.scss'],
standalone: true,
imports: [FormsModule, FlexLayoutModule, MatButtonModule, MatFormFieldModule, MatInputModule]
})
export class ChatInputComponent {
protected response: string = '';
@Input() submitDisabled: boolean = false;
@Output() submitEvent: EventEmitter<string> = new EventEmitter<string>();

protected keyPressed(event: KeyboardEvent): void {
if (event.key === 'Enter') {
event.preventDefault();
if (this.response.length > 0 && !this.submitDisabled) {
this.submit();
}
}
}

protected submit(): void {
this.submitEvent.emit(this.response);
this.response = '';
}
}
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { ComputerAvatarSettings } from './ComputerAvatarSettings';

export interface ComputerAvatarComponentContent {
computerAvatarSettings: ComputerAvatarSettings;
isComputerAvatarEnabled: boolean;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { ComputerAvatarComponentContent } from './computer-avatar-component-content';

export class ComputerAvatarComponent {
content: ComputerAvatarComponentContent;

isComputerAvatarEnabled(): boolean {
return this.content.isComputerAvatarEnabled;
}

isComputerAvatarPromptAvailable(): boolean {
const computerAvatarPrompt = this.content.computerAvatarSettings.prompt;
return computerAvatarPrompt != null && computerAvatarPrompt !== '';
}

isOnlyOneComputerAvatarAvailable(): boolean {
return this.content.computerAvatarSettings.ids.length === 1;
}

isUseGlobalComputerAvatar(): boolean {
return this.content.computerAvatarSettings.useGlobalComputerAvatar;
}

getComputerAvatarInitialResponse(): string {
return this.content.computerAvatarSettings.initialResponse;
}
}
Loading

0 comments on commit 6348a59

Please sign in to comment.