Skip to content

Commit

Permalink
Iris: Add chat in lectures (#9740)
Browse files Browse the repository at this point in the history
  • Loading branch information
isabellagessl authored Feb 4, 2025
1 parent 3358861 commit f77fb84
Show file tree
Hide file tree
Showing 46 changed files with 1,387 additions and 35 deletions.
2 changes: 1 addition & 1 deletion gradle/jacoco.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ ext {
],
"iris" : [
"INSTRUCTION": 0.74,
"CLASS": 25
"CLASS": 26
],
"lecture" : [
"INSTRUCTION": 0.86,
Expand Down
2 changes: 1 addition & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ module.exports = {
global: {
// TODO: in the future, the following values should increase to at least 90%
statements: 88.86,
branches: 74.48,
branches: 74.47,
functions: 83.07,
lines: 88.88,
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package de.tum.cit.aet.artemis.iris.domain.session;

import java.util.Optional;

import jakarta.persistence.DiscriminatorValue;
import jakarta.persistence.Entity;
import jakarta.persistence.ManyToOne;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;

import de.tum.cit.aet.artemis.core.domain.User;
import de.tum.cit.aet.artemis.lecture.domain.Lecture;

@Entity
@DiscriminatorValue("LECTURE_CHAT")
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class IrisLectureChatSession extends IrisChatSession {

@ManyToOne
@JsonIgnore
private Lecture lecture;

public IrisLectureChatSession() {
}

public IrisLectureChatSession(Lecture lecture, User user) {
super(user);
this.lecture = lecture;
}

public Lecture getLecture() {
return lecture;
}

public void setLecture(Lecture lecture) {
this.lecture = lecture;
}

@Override
public String toString() {
return "IrisLectureChatSession{" + "user=" + Optional.ofNullable(getUser()).map(User::getLogin).orElse("null") + ", lecture=" + lecture + '}';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
@JsonSubTypes({
@JsonSubTypes.Type(value = IrisExerciseChatSession.class, name = "chat"), // TODO: Legacy. Should ideally be "exercise_chat"
@JsonSubTypes.Type(value = IrisCourseChatSession.class, name = "course_chat"),
@JsonSubTypes.Type(value = IrisLectureChatSession.class, name = "lecture_chat"),
})
// @formatter:on
@JsonInclude(JsonInclude.Include.NON_EMPTY)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ public class IrisCourseSettings extends IrisSettings {
@JoinColumn(name = "iris_lecture_ingestion_settings_id")
private IrisLectureIngestionSubSettings irisLectureIngestionSettings;

@OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
@JoinColumn(name = "iris_lecture_chat_settings_id")
private IrisLectureChatSubSettings irisLectureChatSettings;

@OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
@JoinColumn(name = "iris_faq_ingestion_settings_id")
private IrisFaqIngestionSubSettings irisFaqIngestionSettings;
Expand Down Expand Up @@ -86,6 +90,16 @@ public void setIrisTextExerciseChatSettings(IrisTextExerciseChatSubSettings iris
this.irisTextExerciseChatSettings = irisTextExerciseChatSettings;
}

@Override
public IrisLectureChatSubSettings getIrisLectureChatSettings() {
return irisLectureChatSettings;
}

@Override
public void setIrisLectureChatSettings(IrisLectureChatSubSettings irisLectureChatSettings) {
this.irisLectureChatSettings = irisLectureChatSettings;
}

@Override
public IrisCourseChatSubSettings getIrisCourseChatSettings() {
return irisCourseChatSettings;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,16 @@ public IrisLectureIngestionSubSettings getIrisLectureIngestionSettings() {
public void setIrisLectureIngestionSettings(IrisLectureIngestionSubSettings irisLectureIngestionSettings) {
}

@Override
public IrisLectureChatSubSettings getIrisLectureChatSettings() {
return null;
}

@Override
public void setIrisLectureChatSettings(IrisLectureChatSubSettings irisLectureChatSettings) {
// Empty because exercises don't have lecture chat settings
}

@Override
public IrisChatSubSettings getIrisChatSettings() {
return irisChatSettings;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ public class IrisGlobalSettings extends IrisSettings {
@JoinColumn(name = "iris_lecture_ingestion_settings_id")
private IrisLectureIngestionSubSettings irisLectureIngestionSettings;

@OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
@JoinColumn(name = "iris_lecture_chat_settings_id")
private IrisLectureChatSubSettings irisLectureChatSettings;

@OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER, optional = false)
@JoinColumn(name = "iris_faq_ingestion_settings_id")
private IrisFaqIngestionSubSettings irisFaqIngestionSubSettings;
Expand Down Expand Up @@ -73,6 +77,16 @@ public void setIrisTextExerciseChatSettings(IrisTextExerciseChatSubSettings iris
this.irisTextExerciseChatSettings = irisTextExerciseChatSettings;
}

@Override
public IrisLectureChatSubSettings getIrisLectureChatSettings() {
return irisLectureChatSettings;
}

@Override
public void setIrisLectureChatSettings(IrisLectureChatSubSettings irisLectureChatSettings) {
this.irisLectureChatSettings = irisLectureChatSettings;
}

@Override
public IrisCourseChatSubSettings getIrisCourseChatSettings() {
return irisCourseChatSettings;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package de.tum.cit.aet.artemis.iris.domain.settings;

import java.util.SortedSet;
import java.util.TreeSet;

import jakarta.annotation.Nullable;
import jakarta.persistence.Column;
import jakarta.persistence.Convert;
import jakarta.persistence.DiscriminatorValue;
import jakarta.persistence.Entity;
import jakarta.validation.constraints.Min;

import com.fasterxml.jackson.annotation.JsonInclude;

/**
* An {@link IrisSubSettings} implementation for the settings for the chat in a text exercise.
*/
@Entity
@DiscriminatorValue("LECTURE_CHAT")
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class IrisLectureChatSubSettings extends IrisSubSettings {

/**
* Maximum number of messages allowed within the specified timeframe.
* Must be a positive integer or null to disable rate limiting.
*/
@Nullable
@Min(1)
@Column(name = "rate_limit")
private Integer rateLimit;

/**
* Timeframe in hours for the rate limit.
* Must be a positive integer when rate limit is set.
*/
@Nullable
@Min(1)
@Column(name = "rate_limit_timeframe_hours")
private Integer rateLimitTimeframeHours;

@Nullable
@Column(name = "enabled_for_categories")
@Convert(converter = IrisListConverter.class)
private SortedSet<String> enabledForCategories = new TreeSet<>();

@Nullable
public Integer getRateLimit() {
return rateLimit;
}

public void setRateLimit(@Nullable Integer rateLimit) {
this.rateLimit = rateLimit;
}

@Nullable
public Integer getRateLimitTimeframeHours() {
return rateLimitTimeframeHours;
}

public void setRateLimitTimeframeHours(@Nullable Integer rateLimitTimeframeHours) {
this.rateLimitTimeframeHours = rateLimitTimeframeHours;
}

@Nullable
public SortedSet<String> getEnabledForCategories() {
return enabledForCategories;
}

public void setEnabledForCategories(@Nullable SortedSet<String> enabledForCategories) {
this.enabledForCategories = enabledForCategories;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
@JsonSubTypes({
@JsonSubTypes.Type(value = IrisGlobalSettings.class, name = "global"),
@JsonSubTypes.Type(value = IrisCourseSettings.class, name = "course"),
@JsonSubTypes.Type(value = IrisExerciseSettings.class, name = "exercise")
@JsonSubTypes.Type(value = IrisExerciseSettings.class, name = "exercise"),
})
// @formatter:on
@JsonInclude(JsonInclude.Include.NON_EMPTY)
Expand All @@ -57,6 +57,10 @@ public abstract class IrisSettings extends DomainObject {

public abstract void setIrisLectureIngestionSettings(IrisLectureIngestionSubSettings irisLectureIngestionSettings);

public abstract IrisLectureChatSubSettings getIrisLectureChatSettings();

public abstract void setIrisLectureChatSettings(IrisLectureChatSubSettings irisLectureChatSettings);

public abstract IrisCompetencyGenerationSubSettings getIrisCompetencyGenerationSettings();

public abstract void setIrisCompetencyGenerationSettings(IrisCompetencyGenerationSubSettings irisCompetencyGenerationSubSettings);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
@JsonSubTypes.Type(value = IrisCourseChatSubSettings.class, name = "course-chat"),
@JsonSubTypes.Type(value = IrisLectureIngestionSubSettings.class, name = "lecture-ingestion"),
@JsonSubTypes.Type(value = IrisCompetencyGenerationSubSettings.class, name = "competency-generation"),
@JsonSubTypes.Type(value = IrisLectureChatSubSettings.class, name = "lecture-chat"),
@JsonSubTypes.Type(value = IrisFaqIngestionSubSettings.class, name = "faq-ingestion"),
})
// @formatter:on
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@

public enum IrisSubSettingsType {
CHAT, // TODO: Rename to PROGRAMMING_EXERCISE_CHAT
TEXT_EXERCISE_CHAT, COURSE_CHAT, COMPETENCY_GENERATION, LECTURE_INGESTION, FAQ_INGESTION
TEXT_EXERCISE_CHAT, COURSE_CHAT, COMPETENCY_GENERATION, LECTURE_INGESTION, LECTURE_CHAT, FAQ_INGESTION
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package de.tum.cit.aet.artemis.iris.dto;

import com.fasterxml.jackson.annotation.JsonInclude;

@JsonInclude(JsonInclude.Include.NON_EMPTY)
public record IrisCombinedLectureChatSubSettingsDTO(boolean enabled, Integer rateLimit, Integer rateLimitTimeframeHours) {

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public record IrisCombinedSettingsDTO(
IrisCombinedCourseChatSubSettingsDTO irisCourseChatSettings,
IrisCombinedLectureIngestionSubSettingsDTO irisLectureIngestionSettings,
IrisCombinedCompetencyGenerationSubSettingsDTO irisCompetencyGenerationSettings,
IrisCombinedLectureChatSubSettingsDTO irisLectureChatSettings,
IrisCombinedFaqIngestionSubSettingsDTO irisFaqIngestionSettings
) {}
// @formatter:on
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package de.tum.cit.aet.artemis.iris.repository;

import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_IRIS;
import static org.springframework.data.jpa.repository.EntityGraph.EntityGraphType.LOAD;

import java.util.Collections;
import java.util.List;

import org.springframework.context.annotation.Profile;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.stereotype.Repository;

import de.tum.cit.aet.artemis.core.domain.DomainObject;
import de.tum.cit.aet.artemis.core.repository.base.ArtemisJpaRepository;
import de.tum.cit.aet.artemis.iris.domain.session.IrisLectureChatSession;

@Repository
@Profile(PROFILE_IRIS)
public interface IrisLectureChatSessionRepository extends ArtemisJpaRepository<IrisLectureChatSession, Long> {

List<IrisLectureChatSession> findByLectureIdAndUserIdOrderByCreationDateDesc(Long lectureId, Long userId);

List<IrisLectureChatSession> findByLectureIdAndUserIdOrderByCreationDateDesc(Long lectureId, Long userId, Pageable pageable);

@EntityGraph(type = LOAD, attributePaths = "messages")
List<IrisLectureChatSession> findSessionsWithMessagesByIdIn(List<Long> ids);

/**
* Finds the latest chat sessions by lecture ID and user ID, including their messages, with pagination support.
* This method avoids in-memory paging by retrieving the session IDs directly from the database.
*
* @param lectureId the ID of the lecture to find the chat sessions for
* @param userId the ID of the user to find the chat sessions for
* @param pageable the pagination information
* @return a list of {@code IrisLectureChatSession} with messages, or an empty list if no sessions are found
*/
default List<IrisLectureChatSession> findLatestSessionsByLectureIdAndUserIdWithMessages(Long lectureId, Long userId, Pageable pageable) {
List<Long> ids = findByLectureIdAndUserIdOrderByCreationDateDesc(lectureId, userId, pageable).stream().map(DomainObject::getId).toList();

if (ids.isEmpty()) {
return Collections.emptyList();
}

return findSessionsWithMessagesByIdIn(ids);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public interface IrisSettingsRepository extends ArtemisJpaRepository<IrisSetting
LEFT JOIN FETCH irisSettings.irisTextExerciseChatSettings
LEFT JOIN FETCH irisSettings.irisLectureIngestionSettings
LEFT JOIN FETCH irisSettings.irisCompetencyGenerationSettings
LEFT JOIN FETCH irisSettings.irisLectureChatSettings
""")
Set<IrisGlobalSettings> findAllGlobalSettings();

Expand All @@ -45,8 +46,9 @@ default IrisGlobalSettings findGlobalSettingsElseThrow() {
LEFT JOIN FETCH irisSettings.irisTextExerciseChatSettings
LEFT JOIN FETCH irisSettings.irisLectureIngestionSettings
LEFT JOIN FETCH irisSettings.irisCompetencyGenerationSettings
LEFT JOIN FETCH irisSettings.irisLectureChatSettings
LEFT JOIN FETCH irisSettings.irisFaqIngestionSettings
WHERE irisSettings.course.id = :courseId
WHERE irisSettings.course.id = :courseId
""")
Optional<IrisCourseSettings> findCourseSettings(@Param("courseId") long courseId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@
import de.tum.cit.aet.artemis.iris.domain.message.IrisMessage;
import de.tum.cit.aet.artemis.iris.domain.session.IrisCourseChatSession;
import de.tum.cit.aet.artemis.iris.domain.session.IrisExerciseChatSession;
import de.tum.cit.aet.artemis.iris.domain.session.IrisLectureChatSession;
import de.tum.cit.aet.artemis.iris.domain.session.IrisSession;
import de.tum.cit.aet.artemis.iris.domain.session.IrisTextExerciseChatSession;
import de.tum.cit.aet.artemis.iris.service.session.IrisChatBasedFeatureInterface;
import de.tum.cit.aet.artemis.iris.service.session.IrisCourseChatSessionService;
import de.tum.cit.aet.artemis.iris.service.session.IrisExerciseChatSessionService;
import de.tum.cit.aet.artemis.iris.service.session.IrisLectureChatSessionService;
import de.tum.cit.aet.artemis.iris.service.session.IrisRateLimitedFeatureInterface;
import de.tum.cit.aet.artemis.iris.service.session.IrisSubFeatureInterface;
import de.tum.cit.aet.artemis.iris.service.session.IrisTextExerciseChatSessionService;
Expand All @@ -38,12 +40,16 @@ public class IrisSessionService {

private final IrisCourseChatSessionService irisCourseChatSessionService;

private final IrisLectureChatSessionService irisLectureChatSessionService;

public IrisSessionService(UserRepository userRepository, IrisTextExerciseChatSessionService irisTextExerciseChatSessionService,
IrisExerciseChatSessionService irisExerciseChatSessionService, IrisCourseChatSessionService irisCourseChatSessionService) {
IrisExerciseChatSessionService irisExerciseChatSessionService, IrisCourseChatSessionService irisCourseChatSessionService,
IrisLectureChatSessionService irisLectureChatSessionService) {
this.userRepository = userRepository;
this.irisTextExerciseChatSessionService = irisTextExerciseChatSessionService;
this.irisExerciseChatSessionService = irisExerciseChatSessionService;
this.irisCourseChatSessionService = irisCourseChatSessionService;
this.irisLectureChatSessionService = irisLectureChatSessionService;
}

/**
Expand Down Expand Up @@ -139,6 +145,7 @@ private <S extends IrisSession> IrisSubFeatureWrapper<S> getIrisSessionSubServic
case IrisTextExerciseChatSession chatSession -> (IrisSubFeatureWrapper<S>) new IrisSubFeatureWrapper<>(irisTextExerciseChatSessionService, chatSession);
case IrisExerciseChatSession chatSession -> (IrisSubFeatureWrapper<S>) new IrisSubFeatureWrapper<>(irisExerciseChatSessionService, chatSession);
case IrisCourseChatSession courseChatSession -> (IrisSubFeatureWrapper<S>) new IrisSubFeatureWrapper<>(irisCourseChatSessionService, courseChatSession);
case IrisLectureChatSession lectureChatSession -> (IrisSubFeatureWrapper<S>) new IrisSubFeatureWrapper<>(irisLectureChatSessionService, lectureChatSession);
case null, default -> throw new BadRequestException("Unknown Iris session type " + session.getClass().getSimpleName());
};
}
Expand Down
Loading

0 comments on commit f77fb84

Please sign in to comment.