Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Iris: Add chat in lectures #9740

Merged
merged 54 commits into from
Feb 4, 2025
Merged
Show file tree
Hide file tree
Changes from 49 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
b4931a5
setup IrisLectureChatSessionService - WIP
isabellagessl Oct 28, 2024
15951ec
Add Iris chat to lecture detail page
sebastianloose Oct 28, 2024
01bfb82
Merge branch 'feature/iris/lecture-chat' of github.com:ls1intum/Artem…
sebastianloose Oct 28, 2024
398de00
WIP
isabellagessl Oct 28, 2024
e3a142a
add IrisLectureChatRessource WIP
isabellagessl Oct 29, 2024
29b82d1
Add iris connection
sebastianloose Oct 31, 2024
e98fded
add settings for lecture chat
isabellagessl Oct 31, 2024
6479306
fix exercise chatbot buttom for exercises
isabellagessl Nov 4, 2024
90ce9b5
Add lecture chat settings
sebastianloose Nov 5, 2024
52f3c5b
Fix settings for lecture chat
sebastianloose Nov 5, 2024
2e0c7ab
add tests for lecture chat
isabellagessl Nov 7, 2024
b41ee6a
Improve code style
sebastianloose Nov 11, 2024
fc70e7b
Remove console prints
sebastianloose Nov 11, 2024
9cfb4d9
Remove console prints
sebastianloose Nov 11, 2024
be57590
implement code rabbit feedback
isabellagessl Nov 11, 2024
3b12778
fix resource and repository annotiation
isabellagessl Nov 11, 2024
dd32e98
fix failed server and client tests
isabellagessl Nov 12, 2024
937fb6f
fix code rabbit feedback and server style
isabellagessl Nov 12, 2024
decc4d1
Add client test
isabellagessl Nov 12, 2024
6405948
Remove not used client code
isabellagessl Nov 12, 2024
6453278
Fix client tests
isabellagessl Nov 12, 2024
51637db
Implement feedback
sebastianloose Nov 14, 2024
bbcc6e9
Merge db migrations
sebastianloose Nov 14, 2024
5a4d2e0
Merge branch 'develop' into feature/iris/lecture-chat
sebastianloose Nov 19, 2024
9b9415e
Merge branch 'develop' into feature/iris/lecture-chat
bassner Nov 28, 2024
3907ee2
Remove not used iris lecture settings
sebastianloose Dec 2, 2024
105959e
integrate feedback
isabellagessl Dec 2, 2024
90561d2
Merge branch 'develop' into feature/iris/lecture-chat
sebastianloose Dec 2, 2024
2c84c7d
Merge branch 'develop' into feature/iris/lecture-chat
sebastianloose Dec 5, 2024
58547e6
Merge branch 'develop' into feature/iris/lecture-chat
isabellagessl Dec 10, 2024
7f094ca
fix client tests
isabellagessl Dec 10, 2024
0c9ac6e
Merge branch 'develop' into feature/iris/lecture-chat
bassner Dec 10, 2024
89e3b49
Remove not related scripts
sebastianloose Dec 10, 2024
1043071
Fix compilation error introduced during merge
sebastianloose Dec 10, 2024
d029dbc
Fix client style tests
sebastianloose Dec 10, 2024
605539b
Merge branch 'develop' into feature/iris/lecture-chat
isabellagessl Jan 13, 2025
6c42f8d
Add unit tests
sebastianloose Jan 13, 2025
0f7335c
Add unit tests
sebastianloose Jan 13, 2025
2f08e5a
Merge branch 'develop' into feature/iris/lecture-chat
isabellagessl Jan 14, 2025
12a0ea3
change import of button
isabellagessl Jan 14, 2025
c9b28bb
Merge branch 'develop' into feature/iris/lecture-chat
isabellagessl Jan 20, 2025
85e60c5
add route prefix
isabellagessl Jan 20, 2025
4ffd467
implement feedback
isabellagessl Jan 20, 2025
57af4f9
fix client tests
isabellagessl Jan 21, 2025
a4ee224
Merge branch 'develop' into feature/iris/lecture-chat
isabellagessl Jan 26, 2025
bfa49a6
Merge branch 'develop' into feature/iris/lecture-chat
HawKhiem Jan 30, 2025
554c9aa
Add auth check for iris lecture chat routes
sebastianloose Jan 30, 2025
67fa828
Minor change
sebastianloose Jan 30, 2025
0f63ef3
Merge branch 'develop' into feature/iris/lecture-chat
sebastianloose Jan 31, 2025
5165c2d
Merge branch 'develop' into feature/iris/lecture-chat
sebastianloose Feb 3, 2025
c458fd4
Fix method description
sebastianloose Feb 3, 2025
ab62ce7
Fix iris client tests
sebastianloose Feb 3, 2025
99c8871
reduce coverage
bassner Feb 3, 2025
561cc57
pls
bassner Feb 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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, optional = false)
@JoinColumn(name = "iris_competency_generation_settings_id")
private IrisCompetencyGenerationSubSettings irisCompetencyGenerationSettings;
Expand Down Expand Up @@ -82,6 +86,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) {
isabellagessl marked this conversation as resolved.
Show resolved Hide resolved
// 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_competency_generation_settings_id")
private IrisCompetencyGenerationSubSettings irisCompetencyGenerationSettings;
Expand Down Expand Up @@ -69,6 +73,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;
}
isabellagessl marked this conversation as resolved.
Show resolved Hide resolved

@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;
}
isabellagessl marked this conversation as resolved.
Show resolved Hide resolved
}
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 @@ -42,7 +42,8 @@
@JsonSubTypes.Type(value = IrisTextExerciseChatSubSettings.class, name = "text-exercise-chat"),
@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 = IrisCompetencyGenerationSubSettings.class, name = "competency-generation"),
@JsonSubTypes.Type(value = IrisLectureChatSubSettings.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 @@ -2,5 +2,5 @@

public enum IrisSubSettingsType {
CHAT, // TODO: Rename to PROGRAMMING_EXERCISE_CHAT
TEXT_EXERCISE_CHAT, COURSE_CHAT, COMPETENCY_GENERATION, LECTURE_INGESTION
TEXT_EXERCISE_CHAT, COURSE_CHAT, COMPETENCY_GENERATION, LECTURE_INGESTION, LECTURE_CHAT
}
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 @@ -9,6 +9,7 @@ public record IrisCombinedSettingsDTO(
IrisCombinedTextExerciseChatSubSettingsDTO irisTextExerciseChatSettings,
IrisCombinedCourseChatSubSettingsDTO irisCourseChatSettings,
IrisCombinedLectureIngestionSubSettingsDTO irisLectureIngestionSettings,
IrisCombinedCompetencyGenerationSubSettingsDTO irisCompetencyGenerationSettings
IrisCombinedCompetencyGenerationSubSettingsDTO irisCompetencyGenerationSettings,
IrisCombinedLectureChatSubSettingsDTO irisLectureChatSettings
) {}
// @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);
}
isabellagessl marked this conversation as resolved.
Show resolved Hide resolved
}
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,6 +46,7 @@ default IrisGlobalSettings findGlobalSettingsElseThrow() {
LEFT JOIN FETCH irisSettings.irisTextExerciseChatSettings
LEFT JOIN FETCH irisSettings.irisLectureIngestionSettings
LEFT JOIN FETCH irisSettings.irisCompetencyGenerationSettings
LEFT JOIN FETCH irisSettings.irisLectureChatSettings
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
Loading