Skip to content

Commit

Permalink
Iris: Add a temporary ChatGPT interface for specific user groups and …
Browse files Browse the repository at this point in the history
…exercises (#10167)
  • Loading branch information
wasnertobias authored Jan 21, 2025
1 parent 07355ec commit 67bd6e7
Show file tree
Hide file tree
Showing 18 changed files with 134 additions and 32 deletions.
4 changes: 2 additions & 2 deletions gradle/jacoco.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ ext {
],
"fileupload" : [
"INSTRUCTION": 0.906,
"CLASS": 0
"CLASS": 1
],
"iris" : [
"INSTRUCTION": 0.795,
"INSTRUCTION": 0.792,
"CLASS": 17
],
"lecture" : [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,9 @@ public final class Constants {

public static final Pattern ALLOWED_CHECKOUT_DIRECTORY = Pattern.compile("[\\w-]+(/[\\w-]+)*$");

// TODO TW: This "feature" is only temporary for a paper.
public static final String ICER_PAPER_FLAG = "ICER 2025 Paper a5157934-9092-4a72-addc-3aaf489debdc";

private Constants() {
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package de.tum.cit.aet.artemis.exercise.web;

import static de.tum.cit.aet.artemis.core.config.Constants.ICER_PAPER_FLAG;
import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE;

import java.time.ZonedDateTime;
Expand All @@ -10,6 +11,7 @@
import java.util.Set;
import java.util.stream.Collectors;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Profile;
Expand Down Expand Up @@ -344,6 +346,13 @@ public ResponseEntity<ExerciseDetailsDTO> getExerciseDetails(@PathVariable Long
.orElse(null);
PlagiarismCaseInfoDTO plagiarismCaseInfo = plagiarismCaseService.getPlagiarismCaseInfoForExerciseAndUser(exercise.getId(), user.getId()).orElse(null);

// TODO TW: This "feature" is only temporary for a paper.
if (StringUtils.contains(exercise.getProblemStatement(), ICER_PAPER_FLAG)) {
if (user.getId() % 3 == 2) {
irisSettings = null;
}
}

return ResponseEntity.ok(new ExerciseDetailsDTO(exercise, irisSettings, plagiarismCaseInfo));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package de.tum.cit.aet.artemis.iris.service.session;

import static de.tum.cit.aet.artemis.core.config.Constants.ICER_PAPER_FLAG;
import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_IRIS;

import java.util.List;
Expand All @@ -8,6 +9,7 @@
import java.util.concurrent.CompletableFuture;
import java.util.stream.IntStream;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Profile;
Expand Down Expand Up @@ -171,6 +173,14 @@ public void requestAndHandleResponse(IrisExerciseChatSession session, Optional<S
var latestSubmission = getLatestSubmissionIfExists(exercise, chatSession.getUser());

var variant = irisSettingsService.getCombinedIrisSettingsFor(session.getExercise(), false).irisChatSettings().selectedVariant();

// TODO TW: This "feature" is only temporary for a paper.
if (StringUtils.contains(exercise.getProblemStatement(), ICER_PAPER_FLAG)) {
if (chatSession.getUser().getId() % 3 == 0) {
variant = "chat-gpt-wrapper";
}
}

pyrisPipelineService.executeExerciseChatPipeline(variant, latestSubmission, exercise, chatSession, event);
}

Expand Down
10 changes: 10 additions & 0 deletions src/main/resources/public/images/chatgpt-temp/ChatGPT_logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/main/webapp/app/app.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,6 @@ export const PROFILE_LTI = 'lti';
export const PROFILE_ATHENA = 'athena';

export const PROFILE_THEIA = 'theia';

// TODO TW: This "feature" is only temporary for a paper.
export const ICER_PAPER_FLAG = 'ICER 2025 Paper a5157934-9092-4a72-addc-3aaf489debdc';
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
<!-- client -->
<div class="chat-header">
<div class="header-start">
<jhi-iris-logo [size]="IrisLogoSize.FLUID" />
<div class="word-iris">Iris</div>
<a [routerLink]="'/about-iris'" target="_blank">
<fa-icon [icon]="faCircleInfo" class="info-button" />
</a>
@if (!isChatGptWrapper) {
<jhi-iris-logo [size]="IrisLogoSize.FLUID" />
} @else {
<!-- TODO TW: This "feature" is only temporary for a paper. -->
<img src="public/images/chatgpt-temp/ChatGPT_logo.svg" alt="Iris Logo" style="height: 27px" class="iris-logo" />
}

<div class="word-iris">{{ isChatGptWrapper ? 'ChatGPT' : 'Iris' }}</div>
@if (!isChatGptWrapper) {
<a [routerLink]="'/about-iris'" target="_blank">
<fa-icon [icon]="faCircleInfo" class="info-button" />
</a>
}
</div>
<div class="d-flex gap-2">
@if (rateLimitInfo.rateLimit > 0) {
Expand Down Expand Up @@ -129,7 +137,12 @@ <h4 class="modal-title">
}
@if (!messages?.length) {
<div class="empty-chat-message">
<jhi-iris-logo [size]="IrisLogoSize.SMALL" />
@if (!isChatGptWrapper) {
<jhi-iris-logo [size]="IrisLogoSize.SMALL" />
} @else {
<!-- TODO TW: This "feature" is only temporary for a paper. -->
<img src="public/images/chatgpt-temp/ChatGPT_logo.svg" alt="Iris Logo" style="height: 35px" class="iris-logo" />
}
<h3 jhiTranslate="artemisApp.iris.chat.helpOffer"></h3>
</div>
}
Expand Down Expand Up @@ -186,7 +199,11 @@ <h3 jhiTranslate="artemisApp.iris.chat.helpOffer"></h3>
this.hasActiveStage
"
/>
<span class="disclaimer-message" jhiTranslate="artemisApp.exerciseChatbot.disclaimer"></span>
@if (!isChatGptWrapper) {
<span class="disclaimer-message" jhiTranslate="artemisApp.exerciseChatbot.disclaimer"></span>
} @else {
<span class="disclaimer-message" jhiTranslate="artemisApp.exerciseChatbot.disclaimerGPT"></span>
}
</div>
}
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -162,25 +162,37 @@
gap: 8px;
}

.bubble-left,
.bubble-right {
--r: 13px; /* the radius */
--t: 10px; /* the size of the tail */

max-width: 100%;
margin-bottom: 10px;
color: var(--bs-body-color);
padding: 10px;
// prettier-ignore
-webkit-mask:
radial-gradient(var(--t) at var(--_d) 0, #0000 98%, #000 102%) var(--_d) 100% / calc(100% - var(--r)) var(--t) no-repeat,
:host {
.bubble-left,
.bubble-right {
--r: 13px; /* the radius */
--t: 10px; /* the size of the tail */

max-width: 100%;
margin-bottom: 10px;
color: var(--bs-body-color);
padding: 10px;
// prettier-ignore
-webkit-mask: radial-gradient(var(--t) at var(--_d) 0, #0000 98%, #000 102%) var(--_d) 100% / calc(100% - var(--r)) var(--t) no-repeat,
conic-gradient(at var(--r) var(--r), #000 75%, #0000 0) calc(var(--r) / -2) calc(var(--r) / -2) padding-box,
radial-gradient(50% 50%, #000 98%, #0000 101%) 0 0 / var(--r) var(--r) space padding-box;
overflow-wrap: break-word;
word-wrap: break-word;
word-break: break-word;
::ng-deep p {
margin-bottom: 0;
overflow-wrap: break-word;
word-wrap: break-word;
word-break: break-word;

::ng-deep > span > p,
::ng-deep > p {
margin-bottom: 0;
}

::ng-deep > span > p:not(:last-child),
::ng-deep > p:not(:last-child) {
margin-bottom: 7px;
}

::ng-deep pre code {
line-height: 0.8;
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ export class IrisBaseChatbotComponent implements OnInit, OnDestroy, AfterViewIni

@Input() fullSize: boolean | undefined;
@Input() showCloseButton = false;
@Input() isChatGptWrapper = false;
@Output() fullSizeToggle = new EventEmitter<void>();
@Output() closeClicked = new EventEmitter<void>();

Expand All @@ -166,6 +167,7 @@ export class IrisBaseChatbotComponent implements OnInit, OnDestroy, AfterViewIni
this.messagesSubscription = this.chatService.currentMessages().subscribe((messages) => {
if (messages.length !== this.messages?.length) {
this.scrollToBottom('auto');
setTimeout(() => this.messageTextarea?.nativeElement?.focus(), 10);
}
this.messages = _.cloneDeep(messages).reverse();
this.messages.forEach((message) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@
}
</div>
<div class="chatbot-button">
<jhi-iris-logo [size]="IrisLogoSize.MEDIUM" [look]="IrisLogoLookDirection.LEFT" (click)="handleButtonClick()" />
@if (isChatGptWrapper) {
<!-- TODO TW: This "feature" is only temporary for a paper. -->
<img src="public/images/chatgpt-temp/ChatGPT_logo.svg" alt="Iris Logo" style="height: 70px" class="iris-logo" (click)="handleButtonClick()" />
} @else {
<jhi-iris-logo [size]="IrisLogoSize.MEDIUM" [look]="IrisLogoLookDirection.LEFT" (click)="handleButtonClick()" />
}
@if (hasNewMessages) {
<fa-icon [icon]="faCircle" size="xl" class="unread-indicator" />
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ export class IrisExerciseChatbotButtonComponent implements OnInit, OnDestroy {
@Input()
mode: ChatServiceMode;

@Input()
isChatGptWrapper: boolean = false; // TODO TW: This "feature" is only temporary for a paper.

dialogRef: MatDialogRef<IrisChatbotWidgetComponent> | null = null;
chatOpen = false;
isOverflowing = false;
Expand Down Expand Up @@ -153,6 +156,7 @@ export class IrisExerciseChatbotButtonComponent implements OnInit, OnDestroy {
scrollStrategy: this.overlay.scrollStrategies.noop(),
position: { bottom: '0px', right: '0px' },
disableClose: true,
data: { isChatGptWrapper: this.isChatGptWrapper },
});
this.dialogRef.afterClosed().subscribe(() => this.handleDialogClose());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
<div class="container" (mouseenter)="toggleScrollLock(true)" (mouseleave)="toggleScrollLock(false)">
<!-- chat box -->
<div class="chat-widget">
<jhi-iris-base-chatbot [fullSize]="fullSize" [showCloseButton]="true" (fullSizeToggle)="toggleFullSize()" (closeClicked)="closeChat()" />
<jhi-iris-base-chatbot
[fullSize]="fullSize"
[showCloseButton]="true"
(fullSizeToggle)="toggleFullSize()"
(closeClicked)="closeChat()"
[isChatGptWrapper]="dialogData?.isChatGptWrapper || false"
/>
<div class="chat-widget-top-resize-area"></div>
</div>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { NavigationStart, Router } from '@angular/router';
import { Subscription } from 'rxjs';
import { ButtonType } from 'app/shared/components/button.component';
import { IrisBaseChatbotComponent } from '../../base-chatbot/iris-base-chatbot.component';
import { MAT_DIALOG_DATA } from '@angular/material/dialog';

@Component({
selector: 'jhi-chatbot-widget',
Expand All @@ -18,6 +19,8 @@ export class IrisChatbotWidgetComponent implements OnDestroy, AfterViewInit {
private router = inject(Router);
private dialog = inject(MatDialog);

dialogData = inject<{ isChatGptWrapper: boolean }>(MAT_DIALOG_DATA);

// User preferences
initialWidth = 400;
initialHeight = 600;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,8 @@ <h4 class="mt-2">
</div>
}
@if (exercise.type === PROGRAMMING && !exercise.exerciseGroup && irisSettings?.irisChatSettings?.enabled) {
<jhi-exercise-chatbot-button [mode]="ChatServiceMode.EXERCISE" />
<jhi-exercise-chatbot-button [mode]="ChatServiceMode.EXERCISE" [isChatGptWrapper]="isChatGptWrapper" />
<!-- TODO TW: This "feature" is only temporary for a paper. -->
}
@if (plagiarismCaseInfo?.verdict === PlagiarismVerdict.NO_PLAGIARISM) {
<a class="btn btn-info btn-sm me-2" [routerLink]="['/courses', courseId, 'plagiarism-cases', plagiarismCaseInfo?.id]">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import { ExerciseCacheService } from 'app/exercises/shared/exercise/exercise-cac
import { IrisSettings } from 'app/entities/iris/settings/iris-settings.model';
import { AbstractScienceComponent } from 'app/shared/science/science.component';
import { ScienceEventType } from 'app/shared/science/science.model';
import { PROFILE_IRIS } from 'app/app.constants';
import { ICER_PAPER_FLAG, PROFILE_IRIS } from 'app/app.constants';
import { ChatServiceMode } from 'app/iris/iris-chat.service';
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { NgClass } from '@angular/common';
Expand All @@ -59,6 +59,7 @@ import { DiscussionSectionComponent } from '../discussion-section/discussion-sec
import { LtiInitializerComponent } from './lti-initializer.component';
import { ArtemisDatePipe } from 'app/shared/pipes/artemis-date.pipe';
import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe';
import { AccountService } from 'app/core/auth/account.service';

interface InstructorActionItem {
routerLink: string;
Expand Down Expand Up @@ -112,6 +113,7 @@ export class CourseExerciseDetailsComponent extends AbstractScienceComponent imp
private quizExerciseService = inject(QuizExerciseService);
private complaintService = inject(ComplaintService);
private artemisMarkdown = inject(ArtemisMarkdownService);
private accountService = inject(AccountService); // TODO TW: This "feature" is only temporary for a paper.

readonly AssessmentType = AssessmentType;
readonly PlagiarismVerdict = PlagiarismVerdict;
Expand All @@ -129,6 +131,7 @@ export class CourseExerciseDetailsComponent extends AbstractScienceComponent imp
readonly isCommunicationEnabled = isCommunicationEnabled;
readonly isMessagingEnabled = isMessagingEnabled;

isChatGptWrapper: boolean = false; // TODO TW: This "feature" is only temporary for a paper.
public learningPathMode = false;
public exerciseId: number;
public courseId: number;
Expand Down Expand Up @@ -222,6 +225,13 @@ export class CourseExerciseDetailsComponent extends AbstractScienceComponent imp
handleNewExercise(newExerciseDetails: ExerciseDetailsType) {
this.exercise = newExerciseDetails.exercise;

// TODO TW: This "feature" is only temporary for a paper.
if (this.exercise.problemStatement?.includes(ICER_PAPER_FLAG)) {
this.accountService.identity().then((user) => {
this.isChatGptWrapper = user && user.id ? user.id % 3 == 0 : false;
});
}

this.filterUnfinishedResults(this.exercise.studentParticipations);
this.mergeResultsAndSubmissionsForParticipations();
this.isAfterAssessmentDueDate = !this.exercise.assessmentDueDate || dayjs().isAfter(this.exercise.assessmentDueDate);
Expand Down
1 change: 1 addition & 0 deletions src/main/webapp/i18n/de/exerciseChatbot.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"rateLimitExceeded": "Du hast die maximale Anzahl von Nachrichten, die du in einem {{ hours }}-Stunden-Zeitfenster an Iris senden kannst, erreicht. Bitte versuche es später erneut!"
},
"disclaimer": "Iris kann Fehler machen. Überprüfe wichtige Informationen.",
"disclaimerGPT": "ChatGPT kann Fehler machen. Überprüfe wichtige Informationen.",
"tutorFirstMessage": "Hallo, ich bin Iris! Ich kann dir bei deiner Programmieraufgabe helfen. Du kannst <a href='/about-iris' target='_blank'>hier</a> mehr über mich erfahren.",
"codeEditorFirstMessage": "Hallo, ich bin Iris! Ich kann dir bei der Bearbeitung von Programmieraufgaben helfen. Ich kann zum Beispiel: <ul><li>eine neue Aufgabe erstellen</li><li><a href='/' target='_blank'>eine Variante von einer bestehenden Aufgabe erstellen</a></li><li>eine bestehende Aufgabe neuen Anforderungen anpassen</li></ul> Du kannst <a href='/about-iris' target='_blank'>hier</a> mehr über mich erfahren.",
"rateLimitTooltip": "Dies ist die maximale Anzahl von Nachrichten, die du in einem {{ hours }}-Stunden-Zeitfenster an Iris senden kannst."
Expand Down
1 change: 1 addition & 0 deletions src/main/webapp/i18n/en/exerciseChatbot.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"rateLimitExceeded": "You have reached the maximum number of messages you can send to Iris in a {{ hours }} hour window. Please try again later!"
},
"disclaimer": "Iris can make mistakes. Consider checking important information.",
"disclaimerGPT": "ChatGPT can make mistakes. Consider checking important information.",
"tutorFirstMessage": "Hi, I'm Iris! I can help you with your programming exercise. You can learn more about me <a href='/about-iris' target='_blank'>here</a>.",
"codeEditorFirstMessage": "Hi, I'm Iris! I can help you to create programming exercises. For example: <ul><li>create a brand-new exercise</li><li>{link:create a variant of another existing exercise}</li><li>adapt an existing exercise to new requirements</li></ul> You can learn more about me <a href='/about-iris' target='_blank'>here</a>.",
"rateLimitTooltip": "This is the maximum number of messages you can send to Iris in a {{ hours }} hour window."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { IrisChatbotWidgetComponent } from 'app/iris/exercise-chatbot/widget/chatbot-widget.component';
import { IrisChatService } from 'app/iris/iris-chat.service';
import { MockComponent, MockProvider } from 'ng-mocks';
import { MatDialog } from '@angular/material/dialog';
import { MAT_DIALOG_DATA, MatDialog } from '@angular/material/dialog';
import { Router } from '@angular/router';
import { of } from 'rxjs';
import { By } from '@angular/platform-browser';
Expand All @@ -16,7 +16,12 @@ describe('IrisChatbotWidgetComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [IrisChatbotWidgetComponent, MockComponent(IrisBaseChatbotComponent)],
providers: [MockProvider(IrisChatService), { provide: MatDialog, useValue: { closeAll: jest.fn() } }, { provide: Router, useValue: { events: of() } }],
providers: [
MockProvider(IrisChatService),
{ provide: MatDialog, useValue: { closeAll: jest.fn() } },
{ provide: Router, useValue: { events: of() } },
{ provide: MAT_DIALOG_DATA, useValue: { isChatGptWrapper: false } },
],
}).compileComponents();

fixture = TestBed.createComponent(IrisChatbotWidgetComponent);
Expand Down

0 comments on commit 67bd6e7

Please sign in to comment.