diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/config/SecurityConfig.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/config/SecurityConfig.kt index 534860a..a35c92b 100644 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/config/SecurityConfig.kt +++ b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/config/SecurityConfig.kt @@ -3,6 +3,8 @@ package de.niklaskerkhoff.tutorassistantappservice.config import de.niklaskerkhoff.tutorassistantappservice.lib.security.KeycloakJwtAuthenticationConverter import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration +import org.springframework.context.annotation.Profile +import org.springframework.core.env.Environment import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity @@ -18,10 +20,12 @@ import org.springframework.web.filter.CorsFilter @EnableMethodSecurity class SecurityConfig { @Bean - fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + fun securityFilterChain(http: HttpSecurity, env: Environment): SecurityFilterChain { http { csrf { disable() } -// cors { } + if (!env.activeProfiles.contains("prod")) { + cors { } + } authorizeRequests { authorize(anyRequest, authenticated) } @@ -37,22 +41,23 @@ class SecurityConfig { return http.build() } -// @Bean -// fun corsFilter(): CorsFilter { -// val source = UrlBasedCorsConfigurationSource() -// val config = CorsConfiguration() -// config.allowCredentials = true -// config.allowedOrigins = listOf( -// "http://localhost:5173", -// "http://localhost:5174", -// ) -// config.allowedHeaders = listOf( -// "Origin", "Content-Type", "Accept", "Authorization", -// "Access-Control-Allow-Origin" -// ) -// config.allowedMethods = -// listOf("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS") -// source.registerCorsConfiguration("/**", config) -// return CorsFilter(source) -// } + @Bean + @Profile("!prod") + fun corsFilter(): CorsFilter { + val source = UrlBasedCorsConfigurationSource() + val config = CorsConfiguration() + config.allowCredentials = true + config.allowedOrigins = listOf( + "http://localhost:5173", + "http://localhost:5174", + ) + config.allowedHeaders = listOf( + "Origin", "Content-Type", "Accept", "Authorization", + "Access-Control-Allow-Origin" + ) + config.allowedMethods = + listOf("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS") + source.registerCorsConfiguration("/**", config) + return CorsFilter(source) + } } diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/config/WebClientConfig.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/config/WebClientConfig.kt index b604036..ff298a2 100644 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/config/WebClientConfig.kt +++ b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/config/WebClientConfig.kt @@ -9,7 +9,7 @@ class WebClientConfig { @Bean fun webClient(): WebClient { return WebClient.builder() - .codecs { it.defaultCodecs().maxInMemorySize(10 * 1024 * 1024) } + .codecs { it.defaultCodecs().maxInMemorySize(10 * 1024 * 1024) } // 10MB .build() } } diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/app_components/AppController.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/app_components/AppController.kt index b8dad03..74325ff 100644 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/app_components/AppController.kt +++ b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/app_components/AppController.kt @@ -2,5 +2,8 @@ package de.niklaskerkhoff.tutorassistantappservice.lib.app_components import de.niklaskerkhoff.tutorassistantappservice.lib.logging.Logger -abstract class AppController : Logger { -} +/** + * Abstract class every controller should inherit from. + * Provides base functionality and ensures extensibility. + */ +abstract class AppController : Logger diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/app_components/AppService.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/app_components/AppService.kt index e6672a9..a241c6f 100644 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/app_components/AppService.kt +++ b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/app_components/AppService.kt @@ -2,5 +2,8 @@ package de.niklaskerkhoff.tutorassistantappservice.lib.app_components import de.niklaskerkhoff.tutorassistantappservice.lib.logging.Logger -abstract class AppService : Logger { -} +/** + * Abstract class every service should inherit from. + * Provides base functionality and ensures extensibility. + */ +abstract class AppService : Logger diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/entities/AppEntity.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/entities/AppEntity.kt index d4640fa..1129e16 100644 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/entities/AppEntity.kt +++ b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/entities/AppEntity.kt @@ -9,29 +9,50 @@ import org.springframework.data.jpa.domain.support.AuditingEntityListener import java.time.Instant import java.util.* +/** + * Abstract class every entity should inherit from. + * Provides entity-id and JPA-Auditing. + */ @Entity @EntityListeners(AuditingEntityListener::class) @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) abstract class AppEntity { + /** + * ID of the entity. This property is used as the primary key. + * This property is set by JPA-Auditing. It must not be modified manually! + */ @Id @GeneratedValue(strategy = GenerationType.UUID) open val id: UUID? = null -// @Version -// open val version = 0 - + /** + * Date, the entity is created in the database. + * This property is set by JPA-Auditing. It must not be modified manually! + */ @CreatedDate open var createdDate: Instant? = null protected set + /** + * Date, the entity is updated in the database lastly. + * This property is set by JPA-Auditing. It must not be modified manually! + */ @LastModifiedDate open var lastModifiedDate: Instant? = null protected set + /** + * ID of the user whose request created the entity in the database. + * This property is set by JPA-Auditing. It must not be modified manually! + */ @CreatedBy open var createdBy: String? = null protected set + /** + * ID of the user whose request updated the entity in the database lastly. + * This property is set by JPA-Auditing. It must not be modified manually! + */ @LastModifiedBy open var lastModifiedBy: String? = null protected set diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/entities/AppEntityRepo.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/entities/AppEntityRepo.kt index 7fe8d8a..4dbd8a8 100644 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/entities/AppEntityRepo.kt +++ b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/entities/AppEntityRepo.kt @@ -4,5 +4,9 @@ import org.springframework.data.jpa.repository.JpaRepository import org.springframework.data.repository.NoRepositoryBean import java.util.* +/** + * Interface from which every entity's repository should inherit from. + * Leads to concise implementation and can provide additional methods through extension functions. + */ @NoRepositoryBean interface AppEntityRepo : JpaRepository diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/entities/AppEntityRepoExtensions.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/entities/AppEntityRepoExtensions.kt index 4fe4810..e6b6dfc 100644 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/entities/AppEntityRepoExtensions.kt +++ b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/entities/AppEntityRepoExtensions.kt @@ -3,4 +3,9 @@ package de.niklaskerkhoff.tutorassistantappservice.lib.entities import org.springframework.data.repository.findByIdOrNull import java.util.* +/** + * @param id of the Entity which is searched. + * @returns Entity of the particular inherited AppEntityRepo with the given id. + * @throws IllegalArgumentException iff the id is not found. + */ fun AppEntityRepo.findByIdOrThrow(id: UUID) = findByIdOrNull(id) ?: throw IllegalArgumentException(id.toString()) diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/exceptions/ResponseStatusExceptions.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/exceptions/ResponseStatusExceptions.kt index f8cd5cd..7aaac60 100644 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/exceptions/ResponseStatusExceptions.kt +++ b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/exceptions/ResponseStatusExceptions.kt @@ -3,4 +3,9 @@ package de.niklaskerkhoff.tutorassistantappservice.lib.exceptions import org.springframework.http.HttpStatus import org.springframework.web.server.ResponseStatusException +/** + * This exception acts as default exception if something goes wrong. + * + * @param message to specify the error. + */ class BadRequestException(message: String) : ResponseStatusException(HttpStatus.BAD_REQUEST, message) diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/filestore/FileStoreModel.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/filestore/FileStoreModel.kt index 2c5d5b4..f1642c5 100644 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/filestore/FileStoreModel.kt +++ b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/filestore/FileStoreModel.kt @@ -5,14 +5,34 @@ import jakarta.persistence.Embeddable import jakarta.persistence.Embedded import jakarta.persistence.Entity +/** + * Instances of these class are stored in the database as references to the files stored in the file store. + */ @Entity class FileStoreFileReference( + /** + * @see FileStoreAssignment + */ @Embedded val assignment: FileStoreAssignment, + /** + * @see FileStoreUpload + */ @Embedded val upload: FileStoreUpload, + + /** + * URL under which the file ist accessible. + */ val storeUrl: String, + + /** + * For using a different name than the original file name. + */ val displayName: String = upload.name ) : AppEntity() +/** + * Result of the file store for an assignment. + */ @Embeddable data class FileStoreAssignment( val fid: String, @@ -21,6 +41,9 @@ data class FileStoreAssignment( val count: Int, ) +/** + * Result of the file store for an upload. + */ @Embeddable data class FileStoreUpload( val name: String, @@ -28,6 +51,9 @@ data class FileStoreUpload( val eTag: String, ) +/** + * Result of the file store for a deletion. + */ data class FileStoreDelete( val size: Long, ) diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/filestore/FileStoreService.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/filestore/FileStoreService.kt index b1df683..bf37f61 100644 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/filestore/FileStoreService.kt +++ b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/filestore/FileStoreService.kt @@ -12,6 +12,9 @@ import org.springframework.web.multipart.MultipartFile import org.springframework.web.reactive.function.client.WebClient import java.util.* +/** + * Provides file store interaction. + */ @Service class FileStoreService( private val webClient: WebClient, @@ -20,8 +23,17 @@ class FileStoreService( @Value("\${app.seaweedfs.master-url}") private lateinit var masterUrl: String + /** + * @returns all stored FileStoreFileReferences. + */ fun listFiles(): List = fileStoreFileReferenceDefaultRepo.findAll() + /** + * Loads the file content. + * + * @param id of the FileStoreFileReference whose file content shall be loaded. + * @returns Pair of the FileStoreFileReference and content as InputStreamResource. + */ fun getFileById(id: UUID): Pair { val fileReference = fileStoreFileReferenceDefaultRepo.findByIdOrThrow(id) @@ -34,6 +46,14 @@ class FileStoreService( return Pair(fileReference, InputStreamResource(fileBytes.inputStream())) } + /** + * Assigns and uploads a file. + * + * @see assign + * @see upload + * @param file to be uploaded. + * @param displayName name of the file under which it is uploaded so the original file name must not be changed. + */ fun assignAndUpload(file: MultipartFile, displayName: String? = null): FileStoreFileReference { val assignment = assign() val storeUrl = "http://${assignment.publicUrl}/${assignment.fid}" @@ -44,6 +64,11 @@ class FileStoreService( return fileStoreFileReferenceDefaultRepo.save(fileReference) } + /** + * Assigns a storage location for a new file. + * + * @returns an FileStoreAssignment containing a reference (url) to the storage location. + */ fun assign(): FileStoreAssignment { return webClient.get() .uri(masterUrl) @@ -52,6 +77,13 @@ class FileStoreService( .block() ?: throw EmptyResponseBodyException() } + /** + * Uploads a file to the file store. + * + * @param storeUrl to which the file shall be uploaded. + * @param file to be uploaded. + * @returns FileStoreUpload as the result of the file store. + */ fun upload(storeUrl: String, file: MultipartFile): FileStoreUpload { val body: MultiValueMap = LinkedMultiValueMap() body.add("file", file.resource) @@ -65,6 +97,12 @@ class FileStoreService( .block() ?: throw EmptyResponseBodyException() } + /** + * Deletes a file from a file from the file store and the FileStoreFileReference from the database. + * + * @param id of the FileStoreFileReference. + * @returns FileStoreDelete as the result of the file store. + */ fun deleteById(id: UUID): FileStoreDelete { val fileReference = fileStoreFileReferenceDefaultRepo.findByIdOrThrow(id) diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/logging/Logger.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/logging/Logger.kt index bd8c9e9..c14ba65 100644 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/logging/Logger.kt +++ b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/logging/Logger.kt @@ -3,6 +3,9 @@ package de.niklaskerkhoff.tutorassistantappservice.lib.logging import org.slf4j.Logger import org.slf4j.LoggerFactory +/** + * Provides concise logging. + */ interface Logger { val log: Logger get() = LoggerFactory.getLogger(javaClass) } diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/logging/RequestLoggingFilter.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/logging/RequestLoggingFilter.kt index 910785d..2465c85 100644 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/logging/RequestLoggingFilter.kt +++ b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/logging/RequestLoggingFilter.kt @@ -6,6 +6,9 @@ import jakarta.servlet.http.HttpServletResponse import org.springframework.stereotype.Component import org.springframework.web.filter.OncePerRequestFilter +/** + * OncePerRequestFilter for logging base information about incoming requests and outgoing responses. + */ @Component class RequestLoggingFilter : OncePerRequestFilter(), Logger { diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/security/KeycloakJwtAuthenticationConverter.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/security/KeycloakJwtAuthenticationConverter.kt index 055971a..2213a47 100644 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/security/KeycloakJwtAuthenticationConverter.kt +++ b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/security/KeycloakJwtAuthenticationConverter.kt @@ -6,7 +6,9 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority import org.springframework.security.oauth2.jwt.Jwt import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter - +/** + * Provides the claims of a Jwt generated by Keycloak to Spring Security. + */ class KeycloakJwtAuthenticationConverter : Converter { override fun convert(jwt: Jwt): AbstractAuthenticationToken? { diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/security/SecurityUtils.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/security/SecurityUtils.kt index 6868a40..872a5ae 100644 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/security/SecurityUtils.kt +++ b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/security/SecurityUtils.kt @@ -3,4 +3,8 @@ package de.niklaskerkhoff.tutorassistantappservice.lib.security import org.springframework.security.core.authority.SimpleGrantedAuthority import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken -fun JwtAuthenticationToken.hasAuthority(authority: String) = authorities.contains(SimpleGrantedAuthority(authority)) \ No newline at end of file +/** + * @param authority which is required. + * @returns true if this JwtAuthenticationToken has a given authority, false otherwise. + */ +fun JwtAuthenticationToken.hasAuthority(authority: String) = authorities.contains(SimpleGrantedAuthority(authority)) diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/webclient/WebClientExceptions.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/webclient/WebClientExceptions.kt index 27911cd..53736f7 100644 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/webclient/WebClientExceptions.kt +++ b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/lib/webclient/WebClientExceptions.kt @@ -1,3 +1,6 @@ package de.niklaskerkhoff.tutorassistantappservice.lib.webclient +/** + * Exception to be thrown when the response body of a request is empty. + */ class EmptyResponseBodyException : RuntimeException("Response body must not be null") diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/calendar/CalendarController.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/calendar/CalendarController.kt index 513962e..50cdddc 100644 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/calendar/CalendarController.kt +++ b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/calendar/CalendarController.kt @@ -1,6 +1,5 @@ package de.niklaskerkhoff.tutorassistantappservice.modules.calendar -import de.niklaskerkhoff.tutorassistantappservice.modules.calendar.entities.Calendar import org.springframework.security.access.prepost.PreAuthorize import org.springframework.web.bind.annotation.* @@ -9,14 +8,16 @@ import org.springframework.web.bind.annotation.* class CalendarController( private val calendarService: CalendarService ) { + /** + * @see CalendarService.getCalendar + */ @GetMapping - fun getInfo(@RequestParam currentDate: String, @RequestParam currentTitle: String): List = + fun getCalendar(@RequestParam currentDate: String, @RequestParam currentTitle: String): List = calendarService.getCalendar(currentDate, currentTitle) - @GetMapping("all") - @PreAuthorize("hasRole('document-manager')") - fun getAllInfos(): List = calendarService.getAllCalendars() - + /** + * @see CalendarService.loadNewCalendar + */ @PostMapping @PreAuthorize("hasRole('document-manager')") fun reloadInfo() = calendarService.loadNewCalendar() diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/calendar/CalendarService.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/calendar/CalendarService.kt index f0bfd80..1776c44 100644 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/calendar/CalendarService.kt +++ b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/calendar/CalendarService.kt @@ -17,6 +17,13 @@ class CalendarService( @Value("\${app.tutor-assistant.base-url}") private lateinit var baseUrl: String + /** + * Gets the current calendar inserting a given entry. + * + * @param currentDate of the entry to insert. + * @param currentTitle of the entry to insert. + * @returns a list of all calendar entries as CalendarFrontendData sorted by their date and time. + */ fun getCalendar(currentDate: String, currentTitle: String) = calendarRepo.findFirstByOrderByCreatedDateDesc() ?.let { listOf(CalendarEntry(currentTitle, currentDate, null)) + it.entries } @@ -24,8 +31,11 @@ class CalendarService( ?.map { CalendarFrontendData(it, it.date == currentDate) } ?: emptyList() - fun getAllCalendars() = calendarRepo.findAll().sortedBy { it.createdDate } - + /** + * Loads a new calendar from the RAG-Service and saves it. + * + * @returns the new calendar entries sorted by their date and time. + */ fun loadNewCalendar(): List { data class Response(val entries: List) @@ -36,7 +46,7 @@ class CalendarService( .bodyToMono(Response::class.java) .block() ?: throw EmptyResponseBodyException() - log.info("Retrieved calendar from Tutor Assistant. Size: ${response.entries.size}") + log.info("Retrieved calendar from RAG-Service. Size: ${response.entries.size}") return calendarRepo.save(Calendar(response.entries)) .also { log.info("Saved calendar: ${it.id}") } diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/calendar/entities/Calendar.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/calendar/entities/Calendar.kt index f93037c..0a44dd4 100644 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/calendar/entities/Calendar.kt +++ b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/calendar/entities/Calendar.kt @@ -4,8 +4,15 @@ import de.niklaskerkhoff.tutorassistantappservice.lib.entities.AppEntity import jakarta.persistence.ElementCollection import jakarta.persistence.Entity +/** + * Wraps Calendar entries. + */ @Entity class Calendar( + /** + * Entries of the calendar. + * @see CalendarEntry + */ @ElementCollection val entries: List ) : AppEntity() diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/calendar/entities/CalendarEntry.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/calendar/entities/CalendarEntry.kt index f7bd1ec..77563fb 100644 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/calendar/entities/CalendarEntry.kt +++ b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/calendar/entities/CalendarEntry.kt @@ -3,10 +3,26 @@ package de.niklaskerkhoff.tutorassistantappservice.modules.calendar.entities import jakarta.persistence.Column import jakarta.persistence.Embeddable +/** + * Class for representing calendar entries generated by the RAG-Service. + */ @Embeddable data class CalendarEntry( + /** + * title of the entry. + */ @Column(length = 1023) val title: String, + + /** + * date of the entry. + * Not converted to date-time as they are not further processed. + */ val date: String, + + /** + * time of the entry. + * Not converted to date-time as they are not further processed. + */ val time: String?, ) diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/chat/controller/ChatController.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/chat/controller/ChatController.kt index f4d0aa5..b49d69c 100644 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/chat/controller/ChatController.kt +++ b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/chat/controller/ChatController.kt @@ -19,24 +19,51 @@ import java.util.* class ChatController( private val chatService: ChatService ) : AppController() { + + /** + * Chat details. + * Can be accessed if it is their own chat, or they have ROLE_evaluator + * @see ChatService.getChatById + * @returns chat as ChatMainData + */ @GetMapping("{chatId}") fun getChatById(@PathVariable chatId: UUID, jwt: JwtAuthenticationToken): ChatMainData = chatService.getChatById(chatId, jwt.name, !jwt.hasAuthority("ROLE_evaluator")) + /** + * Overview of chats. + * ROLE_evaluator can see all chats, others only their own. + * + * @see ChatService.getAllChats + * @see ChatService.getUsersChats + * @returns a list of chats as ChatBaseData. + */ @GetMapping fun getChats(jwt: JwtAuthenticationToken): List = if (jwt.hasAuthority("ROLE_evaluator")) chatService.getAllChats() else chatService.getUsersChats(jwt.name) + /** + * @see ChatService.createChat + */ @PostMapping fun createChat(jwt: JwtAuthenticationToken): ChatBaseData = chatService.createChat(jwt.name) + /** + * @see ChatService.deleteChat + */ @DeleteMapping("{chatId}") fun deleteChat(@PathVariable chatId: UUID, jwt: JwtAuthenticationToken): Unit = chatService.deleteChat(chatId, jwt.name) + /** + * Send message to a chat. + * + * @see ChatService.sendMessage + * @returns Tokens of the answer as event stream. + */ @PostMapping("{chatId}/messages", produces = [MediaType.TEXT_EVENT_STREAM_VALUE]) - fun sendMessageToExistingChat( + fun sendMessage( @PathVariable chatId: UUID, @RequestBody message: String, jwt: JwtAuthenticationToken diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/chat/controller/MessageController.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/chat/controller/MessageController.kt index 9839962..bfcf8b6 100644 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/chat/controller/MessageController.kt +++ b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/chat/controller/MessageController.kt @@ -6,19 +6,29 @@ import de.niklaskerkhoff.tutorassistantappservice.modules.chat.model.messages.Me import org.springframework.web.bind.annotation.* import java.util.* +/** + * Class for handling feedback for messages + */ @RestController @RequestMapping("chats/messages") class MessageController( private val messageRepo: MessageRepo ) { + /** + * @return feedback by id + */ @GetMapping("{id}") fun getMessageFeedback(@PathVariable id: UUID) = messageRepo.findByIdOrThrow(id).feedback - data class RatingPatch(val rating: Int) + /** + * Updates the rating of the feedback + * + * @returns the feedback + */ @PatchMapping("{id}/feedback-rating") - fun setMessageRating(@PathVariable id: UUID, @RequestBody patch: RatingPatch): MessageFeedback { + fun setFeedbackRating(@PathVariable id: UUID, @RequestBody patch: RatingPatch): MessageFeedback { val message = messageRepo.findByIdOrThrow(id) message.feedback = message.feedback.copy(rating = patch.rating) messageRepo.save(message) @@ -27,8 +37,13 @@ class MessageController( data class ContentPatch(val content: String) + /** + * Updates the content of the feedback + * + * @returns the feedback + */ @PatchMapping("{id}/feedback-content") - fun setMessageRating(@PathVariable id: UUID, @RequestBody patch: ContentPatch): MessageFeedback { + fun setFeedbackContent(@PathVariable id: UUID, @RequestBody patch: ContentPatch): MessageFeedback { val message = messageRepo.findByIdOrThrow(id) message.feedback = message.feedback.copy(content = patch.content.trim()) messageRepo.save(message) diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/chat/model/Chat.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/chat/model/Chat.kt index 7ccbca2..dc93afb 100644 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/chat/model/Chat.kt +++ b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/chat/model/Chat.kt @@ -2,12 +2,24 @@ package de.niklaskerkhoff.tutorassistantappservice.modules.chat.model import de.niklaskerkhoff.tutorassistantappservice.lib.entities.AppEntity import de.niklaskerkhoff.tutorassistantappservice.modules.chat.model.messages.Message -import jakarta.persistence.* -import java.util.* +import jakarta.persistence.Embedded +import jakarta.persistence.Entity +import jakarta.persistence.OneToMany +import jakarta.persistence.OrderBy +/** + * Class for representing chats with LLMs through the RAG-Service. + */ @Entity class Chat( + /** + * Owner of the chat. + */ val userId: String, + + /** + * Summary of the chat based on the messages. + */ @Embedded var summary: ChatSummary? = null, ) : AppEntity() { @@ -16,9 +28,14 @@ class Chat( @OrderBy("createdDate ASC") private val _messages = mutableListOf() + /** + * Messages also referred to as history of the chat. + */ @get:OneToMany val messages: List get() = _messages.toList() + /** + * Adds a message to the chat. + */ fun addMessage(message: Message) = _messages.add(message) - fun removeMessage(messageId: UUID) = _messages.removeIf { it.id == messageId } } diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/chat/model/ChatData.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/chat/model/ChatData.kt index 4a35851..8965cd4 100644 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/chat/model/ChatData.kt +++ b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/chat/model/ChatData.kt @@ -4,6 +4,9 @@ import de.niklaskerkhoff.tutorassistantappservice.modules.chat.model.messages.Me import de.niklaskerkhoff.tutorassistantappservice.modules.chat.model.messages.MessageContext import java.util.* +/** + * Chat data with messages. Used for displaying chat details. + */ data class ChatMainData( val id: UUID?, val summary: ChatSummary?, @@ -19,6 +22,9 @@ data class ChatMainData( ) } +/** + * Chat data without messages. Used for overview of chats. + */ data class ChatBaseData( val id: UUID?, val summary: ChatSummary?, diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/chat/model/ChatService.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/chat/model/ChatService.kt index 1b38751..bf58dcc 100644 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/chat/model/ChatService.kt +++ b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/chat/model/ChatService.kt @@ -41,15 +41,39 @@ class ChatService( @Value("\${app.tutor-assistant.base-url}") private lateinit var baseUrl: String + /** + * Finds the chats of a given user. + * + * @param userId of the user. + * @returns list of users chats. Each chat as ChatBaseData. + */ fun getUsersChats(userId: String) = chatRepo.findByUserIdOrderByCreatedDateDesc(userId).map { ChatBaseData(it) } + /** + * @returns list of all chats. Each chat as ChatBaseData. + */ fun getAllChats() = chatRepo.findAllByOrderByCreatedDateDesc().map { ChatBaseData(it) } + /** + * Finds a chat by its unique id. + * + * @param chatId of the chat. + * @param userId that must match the chats users id in order to retrieve the chat, except... + * @param requiresMatchingUser is false. + * @returns the found chat as ChatMainData. + * @throws IllegalArgumentException iff the id does not exist. + */ fun getChatById(chatId: UUID, userId: String, requiresMatchingUser: Boolean = true): ChatMainData { val chat = chatRepo.findByIdOrThrow(chatId).requireUser(userId, requiresMatchingUser) return ChatMainData(chat) } + /** + * Creates and saves a new chat. + * + * @param userId for which the chat shall be created. + * @returns the new chat as ChatBaseData. + */ fun createChat(userId: String): ChatBaseData { val chat = chatRepo.save(Chat(userId)).also { log.info("Created chat: ${it.id}") @@ -57,6 +81,12 @@ class ChatService( return ChatBaseData(chat) } + /** + * Deletes a chat. + * + * @param chatId of the chat to be deleted. + * @param userId that must match the chats users id in order to perform the deletion. + */ fun deleteChat(chatId: UUID, userId: String) { val chat = chatRepo.findByIdOrThrow(chatId).requireUser(userId) chatRepo.delete(chat).also { @@ -64,6 +94,22 @@ class ChatService( } } + /** + * This method + * * Adds the given message to the chats history + * * Sends it together with the chats history to the RAG-Service + * * Returns the tokens of the response of the RAG-Service + * * Sends MESSAGE_END token when the RAG-Service's response is finished + * * Stores the response of the RAG-Service + * * Loads a new Summary for the new history + * * Sends EVENT_STREAM_END token when Summary is loaded + * * Stores loaded summary + * + * @param chatId of the chat to which the message shall be sent. + * @param message to send. + * @param userId that must match the chats users id in order to send the message. + * @returns an event stream of the RAG-Services Tokens. + */ @Transactional fun sendMessage(chatId: UUID, message: String, userId: String): Flux { val chat = chatRepo.findByIdOrThrow(chatId).requireUser(userId) @@ -74,11 +120,6 @@ class ChatService( val userMessage = Message("user", message) chat.addMessage(userMessage) - /*mono(Dispatchers.IO) { - messageRepo.save(userMessage) - chatRepo.save(chat) - }*/ - messageRepo.save(userMessage).also { log.info("Created user-message: ${it.id} for chat: ${chat.id}") } @@ -173,11 +214,9 @@ class ChatService( private fun getContextFromJson(json: String): MessageContext { val root = objectMapper.readTree(json) return MessageContext( - tutorAssistantId = root.getOrNull("kwargs").getOrNull("metadata").getOrNull("tutorAssistantId")?.asText(), title = root.getOrNull("kwargs").getOrNull("metadata").getOrNull("title")?.asText(), originalKey = root.getOrNull("kwargs").getOrNull("metadata").getOrNull("originalKey")?.asText(), isCalendar = root.getOrNull("kwargs").getOrNull("metadata").getOrNull("isCalendar")?.asBoolean(), - heading = root.getOrNull("kwargs").getOrNull("metadata").getOrNull("heading")?.asText(), page = root.getOrNull("kwargs").getOrNull("metadata").getOrNull("page")?.asInt(), content = root.getOrNull("kwargs").getOrNull("page_content")?.asText(), score = root.getOrNull("kwargs").getOrNull("metadata").getOrNull("score")?.asDouble(), diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/chat/model/ChatSummary.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/chat/model/ChatSummary.kt index 61da65b..99c71b8 100644 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/chat/model/ChatSummary.kt +++ b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/chat/model/ChatSummary.kt @@ -3,7 +3,6 @@ package de.niklaskerkhoff.tutorassistantappservice.modules.chat.model import jakarta.persistence.Column import jakarta.persistence.Embeddable - @Embeddable data class ChatSummary( val title: String, diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/chat/model/messages/Message.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/chat/model/messages/Message.kt index ae4f6e9..ebef334 100644 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/chat/model/messages/Message.kt +++ b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/chat/model/messages/Message.kt @@ -7,10 +7,26 @@ import jakarta.persistence.Entity @Entity class Message( + /** + * Specifies who wrote the message e.g. ai or user + */ val role: String, + + /** + * The actual content of the message that is written by the role + */ @Column(columnDefinition = "text") val content: String, @ElementCollection + + /** + * The contexts based on which the content was created. + * Only present if message from ai. + */ val contexts: List? = null, + + /** + * Feedback the user can provide if the message is from ai + */ var feedback: MessageFeedback = MessageFeedback(0, "") ) : AppEntity() diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/chat/model/messages/MessageContext.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/chat/model/messages/MessageContext.kt index 702b072..cbc9644 100644 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/chat/model/messages/MessageContext.kt +++ b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/chat/model/messages/MessageContext.kt @@ -3,15 +3,39 @@ package de.niklaskerkhoff.tutorassistantappservice.modules.chat.model.messages import jakarta.persistence.Column import jakarta.persistence.Embeddable +/** + * Class for representing sources also referred to as contexts of a message from ai. + */ @Embeddable data class MessageContext( - val tutorAssistantId: String?, + /** + * Title of the context. + */ val title: String?, + + /** + * Key used to open the context. Can be a URL or a file reference id. + */ val originalKey: String?, + + /** + * Specifies if the context is used for generating the calendar. + */ val isCalendar: Boolean?, - val heading: String?, + + /** + * Page number for resources split into pages like PDFs. + */ val page: Int?, + + /** + * Actual content of the context on which similarity was calculated. + */ @Column(columnDefinition = "text") val content: String?, + + /** + * Similarity score that states how similar the query is to the context. + */ val score: Double? ) diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/chat/model/messages/MessageFeedback.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/chat/model/messages/MessageFeedback.kt index da2c42d..f516d80 100644 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/chat/model/messages/MessageFeedback.kt +++ b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/chat/model/messages/MessageFeedback.kt @@ -3,9 +3,20 @@ package de.niklaskerkhoff.tutorassistantappservice.modules.chat.model.messages import jakarta.persistence.Column import jakarta.persistence.Embeddable +/** + * Class for representing feedback the user can provide to messages from ai. + */ @Embeddable data class MessageFeedback( + /** + * Numeric feedback. + */ val rating: Int, + + + /** + * Textual feedback. + */ @Column(name = "feedback_content", columnDefinition = "text") val content: String ) diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/TutorAssistantDocumentService.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/TutorAssistantDocumentService.kt deleted file mode 100644 index d1a2dda..0000000 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/TutorAssistantDocumentService.kt +++ /dev/null @@ -1,48 +0,0 @@ -package de.niklaskerkhoff.tutorassistantappservice.modules.documents - -import de.niklaskerkhoff.tutorassistantappservice.lib.app_components.AppService -import de.niklaskerkhoff.tutorassistantappservice.lib.webclient.EmptyResponseBodyException -import org.springframework.beans.factory.annotation.Value -import org.springframework.core.ParameterizedTypeReference -import org.springframework.stereotype.Service -import org.springframework.web.reactive.function.client.WebClient - -@Service -class TutorAssistantDocumentService( - private val webClient: WebClient -) : AppService() { - @Value("\${app.tutor-assistant.base-url}") - private lateinit var baseUrl: String - - fun addDocument( - title: String, - originalKey: String, - loaderType: String, - loaderParams: Map, - isCalendar: Boolean - ): List { - val requestBody = mapOf( - "title" to title, - "originalKey" to originalKey, - "loaderType" to loaderType, - "loaderParams" to loaderParams, - "isCalendar" to isCalendar - ) - - return webClient.post() - .uri("$baseUrl/documents/add") - .bodyValue(requestBody) - .retrieve() - .bodyToMono(object : ParameterizedTypeReference>() {}) - .block() ?: throw EmptyResponseBodyException() - } - - fun deleteDocument(tutorAssistantIds: List): Boolean { - return webClient.post() - .uri("$baseUrl/documents/delete") - .bodyValue(tutorAssistantIds) - .retrieve() - .bodyToMono(Boolean::class.java) - .block() ?: throw EmptyResponseBodyException() - } -} diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/applications/ApplicationController.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/applications/ApplicationController.kt deleted file mode 100644 index 1ae75b4..0000000 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/applications/ApplicationController.kt +++ /dev/null @@ -1,38 +0,0 @@ -package de.niklaskerkhoff.tutorassistantappservice.modules.documents.applications - -import de.niklaskerkhoff.tutorassistantappservice.modules.documents.applications.entities.toDto -import org.springframework.security.access.prepost.PreAuthorize -import org.springframework.web.bind.annotation.* -import java.util.* - -@RestController -@RequestMapping("documents/applications") -class ApplicationController( - private val applicationService: ApplicationService -) { - @GetMapping("files") - fun getFileDocuments() = applicationService.getFileDocuments().map { it.toDto() } - - @GetMapping("websites") - fun getWebsiteDocuments() = applicationService.getWebsiteDocuments().map { it.toDto() } - - @PostMapping("index") - @PreAuthorize("hasRole('document-manager')") - fun index(): Unit = applicationService.index() - - @PostMapping("files/{id}/reindex") - @PreAuthorize("hasRole('document-manager')") - fun reindexFile(@PathVariable id: UUID): Unit = applicationService.reindexFileDocument(id) - - @PostMapping("websites/{id}/reindex") - @PreAuthorize("hasRole('document-manager')") - fun reindexWebsite(@PathVariable id: UUID): Unit = applicationService.reindexWebsiteDocument(id) - - @DeleteMapping("files/{id}") - @PreAuthorize("hasRole('document-manager')") - fun deleteFile(@PathVariable id: UUID): Unit = applicationService.deleteFileDocument(id) - - @DeleteMapping("websites/{id}") - @PreAuthorize("hasRole('document-manager')") - fun deleteWebsite(@PathVariable id: UUID): Unit = applicationService.deleteWebsiteDocument(id) -} diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/applications/ApplicationDocumentLoader.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/applications/ApplicationDocumentLoader.kt deleted file mode 100644 index 3f484e5..0000000 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/applications/ApplicationDocumentLoader.kt +++ /dev/null @@ -1,7 +0,0 @@ -package de.niklaskerkhoff.tutorassistantappservice.modules.documents.applications - -import de.niklaskerkhoff.tutorassistantappservice.modules.documents.applications.entities.Document - -interface ApplicationDocumentLoader { - fun loadDocuments(): List -} \ No newline at end of file diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/applications/ApplicationService.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/applications/ApplicationService.kt deleted file mode 100644 index 566cc51..0000000 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/applications/ApplicationService.kt +++ /dev/null @@ -1,61 +0,0 @@ -package de.niklaskerkhoff.tutorassistantappservice.modules.documents.applications - -import de.niklaskerkhoff.tutorassistantappservice.lib.app_components.AppService -import de.niklaskerkhoff.tutorassistantappservice.lib.entities.findByIdOrThrow -import de.niklaskerkhoff.tutorassistantappservice.lib.exceptions.BadRequestException -import de.niklaskerkhoff.tutorassistantappservice.modules.documents.TutorAssistantDocumentService -import de.niklaskerkhoff.tutorassistantappservice.modules.documents.applications.entities.* -import org.springframework.stereotype.Service -import java.util.* - -@Service -class ApplicationService( - private val fileDocumentRepo: FileDocumentRepo, - private val websiteDocumentRepo: WebsiteDocumentRepo, - private val applicationDocumentLoader: ApplicationDocumentLoader, - private val applierVisitor: ApplierVisitor, - private val tutorAssistantDocumentService: TutorAssistantDocumentService -) : AppService() { - - fun getFileDocuments(): List = fileDocumentRepo.findAll() - - fun getWebsiteDocuments(): List = websiteDocumentRepo.findAll() - - fun index() { - val documents = applicationDocumentLoader.loadDocuments() - documents.forEach { it.accept(applierVisitor) } - } - - fun reindexFileDocument(id: UUID) = reindex(id, fileDocumentRepo) - - fun reindexWebsiteDocument(id: UUID) = reindex(id, websiteDocumentRepo) - - fun deleteFileDocument(id: UUID) = delete(id, fileDocumentRepo) - - fun deleteWebsiteDocument(id: UUID) = delete(id, websiteDocumentRepo) - - private fun reindex(id: UUID, documentRepo: DocumentRepo) { - val existingDocument = documentRepo.findByIdOrThrow(id) - val title = existingDocument.title - val allDocuments = applicationDocumentLoader.loadDocuments() - val documentToReindex = allDocuments.find { it.title == title } - ?: throw BadRequestException("Document $title not specified in main settings") - - delete(existingDocument, documentRepo) - documentToReindex.accept(applierVisitor) - } - - private fun delete(id: UUID, documentRepo: DocumentRepo) { - val document = documentRepo.findByIdOrThrow(id) - delete(document, documentRepo) - } - - private fun delete(document: T, documentRepo: DocumentRepo) { - tutorAssistantDocumentService.deleteDocument(document.tutorAssistantIds).also { - log.info("Deleted ${document.tutorAssistantIds} from Tutor-Assistant") - } - documentRepo.delete(document).also { - log.info("Deleted document with id ${document.id}") - } - } -} diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/applications/ApplierVisitor.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/applications/ApplierVisitor.kt deleted file mode 100644 index b08abe8..0000000 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/applications/ApplierVisitor.kt +++ /dev/null @@ -1,94 +0,0 @@ -package de.niklaskerkhoff.tutorassistantappservice.modules.documents.applications - -import de.niklaskerkhoff.tutorassistantappservice.lib.logging.Logger -import de.niklaskerkhoff.tutorassistantappservice.modules.documents.TutorAssistantDocumentService -import de.niklaskerkhoff.tutorassistantappservice.modules.documents.applications.entities.* -import org.springframework.stereotype.Component - -@Component -class ApplierVisitor( - private val fileDocumentRepo: FileDocumentRepo, - private val websiteDocumentRepo: WebsiteDocumentRepo, - private val tutorAssistantDocumentService: TutorAssistantDocumentService -) : DocumentVisitor, Logger { - - override fun visit(fileDocument: FileDocument) { - log.info("Visiting fileDocument with title ${fileDocument.title}") - - val existing = fileDocumentRepo.findByTitle(fileDocument.title) - if (existing != null) { - logStopping(fileDocument.title) - return - } - - logContinuing(fileDocument.title) - - val loaderParams = mapOf("url" to fileDocument.fileStoreUrl) - - val tutorAssistantIds = tutorAssistantDocumentService.addDocument( - fileDocument.title, - fileDocument.fileStoreId.toString(), - fileDocument.loaderType, - loaderParams, - fileDocument.isCalendar - ).also { - logAddedToTutorAssistant(fileDocument.title, it) - } - - fileDocument.tutorAssistantIds = tutorAssistantIds - - fileDocumentRepo.save(fileDocument).also { - logSaved(it.title) - } - } - - override fun visit(websiteDocument: WebsiteDocument) { - log.info("Visiting websiteDocument with title ${websiteDocument.title}") - - val existing = websiteDocumentRepo.findByTitle(websiteDocument.title) - if (existing != null) { - logStopping(websiteDocument.title) - return - } - - logContinuing(websiteDocument.title) - - val loaderParams = mapOf( - "url" to websiteDocument.loaderParams.url, - "htmlSelector" to websiteDocument.loaderParams.htmlSelector, - "htmlSelectionIndex" to websiteDocument.loaderParams.htmlSelectionIndex, - ) - - val tutorAssistantIds = tutorAssistantDocumentService.addDocument( - websiteDocument.title, - websiteDocument.loaderParams.url, - websiteDocument.loaderType, - loaderParams, - websiteDocument.isCalendar - ).also { - logAddedToTutorAssistant(websiteDocument.title, it) - } - - websiteDocument.tutorAssistantIds = tutorAssistantIds - - websiteDocumentRepo.save(websiteDocument).also { - logSaved(websiteDocument.title) - } - } - - private fun logContinuing(title: String) { - log.info("$title does not exist, continuing") - } - - private fun logStopping(title: String) { - log.info("$title already exists, stopping") - } - - private fun logAddedToTutorAssistant(title: String, tutorAssistantIds: List) { - log.info("Added $title to Tutor-Assistant, got ${tutorAssistantIds.size} ids") - } - - private fun logSaved(title: String) { - log.info("Saved $title") - } -} diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/applications/entities/Document.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/applications/entities/Document.kt deleted file mode 100644 index 33319a3..0000000 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/applications/entities/Document.kt +++ /dev/null @@ -1,21 +0,0 @@ -package de.niklaskerkhoff.tutorassistantappservice.modules.documents.applications.entities - -import de.niklaskerkhoff.tutorassistantappservice.lib.entities.AppEntity -import jakarta.persistence.ElementCollection -import jakarta.persistence.Entity -import jakarta.persistence.Inheritance -import jakarta.persistence.InheritanceType - -@Entity -@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) -abstract class Document( - open val title: String, - open val loaderType: String, - open val collection: String?, - open val isCalendar: Boolean -) : AppEntity() { - @ElementCollection - open var tutorAssistantIds: List = emptyList() - - abstract fun accept(visitor: DocumentVisitor) -} diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/applications/entities/DocumentRepo.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/applications/entities/DocumentRepo.kt deleted file mode 100644 index 9bd5307..0000000 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/applications/entities/DocumentRepo.kt +++ /dev/null @@ -1,9 +0,0 @@ -package de.niklaskerkhoff.tutorassistantappservice.modules.documents.applications.entities - -import de.niklaskerkhoff.tutorassistantappservice.lib.entities.AppEntityRepo -import org.springframework.data.repository.NoRepositoryBean - -@NoRepositoryBean -interface DocumentRepo : AppEntityRepo { - fun findByTitle(title: String): T? -} diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/applications/entities/DocumentVisitor.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/applications/entities/DocumentVisitor.kt deleted file mode 100644 index 85f1cb9..0000000 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/applications/entities/DocumentVisitor.kt +++ /dev/null @@ -1,6 +0,0 @@ -package de.niklaskerkhoff.tutorassistantappservice.modules.documents.applications.entities - -interface DocumentVisitor { - fun visit(fileDocument: FileDocument) - fun visit(websiteDocument: WebsiteDocument) -} diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/applications/entities/FileDocument.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/applications/entities/FileDocument.kt deleted file mode 100644 index 7d2f9bb..0000000 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/applications/entities/FileDocument.kt +++ /dev/null @@ -1,18 +0,0 @@ -package de.niklaskerkhoff.tutorassistantappservice.modules.documents.applications.entities - -import jakarta.persistence.Entity -import java.util.UUID - -@Entity -class FileDocument( - title: String, - loaderType: String, - collection: String?, - isCalendar: Boolean, - val fileStoreId: UUID, - val fileStoreUrl: String, -) : Document(title, loaderType, collection, isCalendar) { - override fun accept(visitor: DocumentVisitor) { - visitor.visit(this) - } -} diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/applications/entities/FileDocumentDtos.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/applications/entities/FileDocumentDtos.kt deleted file mode 100644 index b80ae36..0000000 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/applications/entities/FileDocumentDtos.kt +++ /dev/null @@ -1,21 +0,0 @@ -package de.niklaskerkhoff.tutorassistantappservice.modules.documents.applications.entities - -import java.util.UUID - -data class FileDocumentDto( - val id: UUID?, - val title: String, - val loaderType: String, - val collection: String?, - val fileStoreId: UUID -) { - constructor(fileDocument: FileDocument) : this( - id = fileDocument.id, - title = fileDocument.title, - loaderType = fileDocument.loaderType, - collection = fileDocument.collection, - fileStoreId = fileDocument.fileStoreId - ) -} - -fun FileDocument.toDto() = FileDocumentDto(this) diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/applications/entities/FileDocumentRepo.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/applications/entities/FileDocumentRepo.kt deleted file mode 100644 index 829b8f2..0000000 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/applications/entities/FileDocumentRepo.kt +++ /dev/null @@ -1,3 +0,0 @@ -package de.niklaskerkhoff.tutorassistantappservice.modules.documents.applications.entities - -interface FileDocumentRepo : DocumentRepo diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/applications/entities/WebsiteDocument.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/applications/entities/WebsiteDocument.kt deleted file mode 100644 index 26b9be6..0000000 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/applications/entities/WebsiteDocument.kt +++ /dev/null @@ -1,24 +0,0 @@ -package de.niklaskerkhoff.tutorassistantappservice.modules.documents.applications.entities - -import jakarta.persistence.Embeddable -import jakarta.persistence.Entity - -@Entity -class WebsiteDocument( - title: String, - loaderType: String, - collection: String?, - isCalendar: Boolean, - val loaderParams: LoaderParams, -) : Document(title, loaderType, collection, isCalendar) { - override fun accept(visitor: DocumentVisitor) { - visitor.visit(this) - } - - @Embeddable - data class LoaderParams( - val url: String, - val htmlSelector: String, - val htmlSelectionIndex: Int - ) -} diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/applications/entities/WebsiteDocumentDtos.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/applications/entities/WebsiteDocumentDtos.kt deleted file mode 100644 index 7c02927..0000000 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/applications/entities/WebsiteDocumentDtos.kt +++ /dev/null @@ -1,21 +0,0 @@ -package de.niklaskerkhoff.tutorassistantappservice.modules.documents.applications.entities - -import java.util.* - -data class WebsiteDocumentDto( - val id: UUID?, - val title: String, - val loaderType: String, - val collection: String?, - val url: String, -) { - constructor(websiteDocument: WebsiteDocument) : this( - id = websiteDocument.id, - title = websiteDocument.title, - loaderType = websiteDocument.loaderType, - collection = websiteDocument.collection, - url = websiteDocument.loaderParams.url - ) -} - -fun WebsiteDocument.toDto() = WebsiteDocumentDto(this) diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/applications/entities/WebsiteDocumentRepo.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/applications/entities/WebsiteDocumentRepo.kt deleted file mode 100644 index 5255ca6..0000000 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/applications/entities/WebsiteDocumentRepo.kt +++ /dev/null @@ -1,3 +0,0 @@ -package de.niklaskerkhoff.tutorassistantappservice.modules.documents.applications.entities - -interface WebsiteDocumentRepo : DocumentRepo diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/resources/ResourceController.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/resources/ResourceController.kt deleted file mode 100644 index 2081675..0000000 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/resources/ResourceController.kt +++ /dev/null @@ -1,55 +0,0 @@ -package de.niklaskerkhoff.tutorassistantappservice.modules.documents.resources - -import de.niklaskerkhoff.tutorassistantappservice.lib.filestore.FileStoreFileReferenceDto -import de.niklaskerkhoff.tutorassistantappservice.lib.filestore.FileStoreService -import de.niklaskerkhoff.tutorassistantappservice.lib.filestore.toDto -import org.springframework.core.io.InputStreamResource -import org.springframework.http.HttpHeaders -import org.springframework.http.MediaType -import org.springframework.http.ResponseEntity -import org.springframework.security.access.prepost.PreAuthorize -import org.springframework.web.bind.annotation.* -import org.springframework.web.multipart.MultipartFile -import java.util.* - -@RestController -@RequestMapping("documents/resources") -class ResourceController( - private val resourceService: ResourceService, - private val fileStoreService: FileStoreService -) { - companion object { - private val CONTENT_TYPES = mapOf( - "pdf" to MediaType.APPLICATION_PDF, - ) - private val DEFAULT_CONTENT_TYPE = MediaType.TEXT_PLAIN - } - - @GetMapping - fun listFiles(): List = fileStoreService.listFiles().map { it.toDto() } - - @GetMapping("{id}") - fun getFile(@PathVariable id: UUID): ResponseEntity { - val fileData = fileStoreService.getFileById(id) - val fileType = fileData.first.displayName.split(".").last() - - return ResponseEntity.ok() - .header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"${fileData.first.displayName}\"") - .contentType(CONTENT_TYPES[fileType] ?: DEFAULT_CONTENT_TYPE) - .body(fileData.second) - } - - @PostMapping - @PreAuthorize("hasRole('document-manager')") - fun addFile( - @RequestPart("file") file: MultipartFile, - ): FileStoreFileReferenceDto { - return fileStoreService.assignAndUpload(file, resourceService.getUniqueFilename(file)).toDto() - } - - @DeleteMapping("{id}") - @PreAuthorize("hasRole('document-manager')") - fun deleteFile(@PathVariable id: UUID) { - fileStoreService.deleteById(id) - } -} diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/resources/ResourceService.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/resources/ResourceService.kt deleted file mode 100644 index 0983669..0000000 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/resources/ResourceService.kt +++ /dev/null @@ -1,41 +0,0 @@ -package de.niklaskerkhoff.tutorassistantappservice.modules.documents.resources - -import de.niklaskerkhoff.tutorassistantappservice.lib.exceptions.BadRequestException -import de.niklaskerkhoff.tutorassistantappservice.lib.filestore.FileStoreFileReferenceDefaultRepo -import org.springframework.stereotype.Service -import org.springframework.web.multipart.MultipartFile - -@Service -class ResourceService( - private val fileReferenceDefaultRepo: FileStoreFileReferenceDefaultRepo -) { - companion object { - private const val UNIQUE_START_N = 2 - } - - fun getUniqueFilename(file: MultipartFile): String { - val filename = file.originalFilename ?: throw BadRequestException("Filename must not be null") - return getUniqueFilename(filename) - } - - private fun getUniqueFilename(filename: String, n: Int? = null): String { - val fileReferences = fileReferenceDefaultRepo.findAllByDisplayName(filename) - if (fileReferences.size > 1) throw UnknownError("Filenames must be unique") - if (fileReferences.isNotEmpty()) { - return if (n == null) getUniqueFilename(filename, UNIQUE_START_N) else getUniqueFilename(filename, n + 1) - } - - return if (n == null) filename else addNumberToFilename(filename, n) - } - - private fun addNumberToFilename(filename: String, number: Int): String { - val dotIndex = filename.lastIndexOf('.') - return if (dotIndex != -1) { - val name = filename.substring(0, dotIndex) - val extension = filename.substring(dotIndex) - "$name.$number$extension" - } else { - "$filename.$number" - } - } -} diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/settings/SettingController.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/settings/SettingController.kt deleted file mode 100644 index 56e9340..0000000 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/settings/SettingController.kt +++ /dev/null @@ -1,25 +0,0 @@ -package de.niklaskerkhoff.tutorassistantappservice.modules.documents.settings - -import de.niklaskerkhoff.tutorassistantappservice.modules.documents.settings.entities.SettingDto -import de.niklaskerkhoff.tutorassistantappservice.modules.documents.settings.entities.toDto -import org.springframework.security.access.prepost.PreAuthorize -import org.springframework.web.bind.annotation.* -import org.springframework.web.multipart.MultipartFile -import java.util.* - -@RestController -@RequestMapping("documents/settings") -@PreAuthorize("hasRole('document-manager')") -class SettingController( - private val settingService: SettingService -) { - - @GetMapping - fun getSettings(): List = settingService.getSettings().map { it.toDto() } - - @PostMapping - fun addSetting(@RequestPart("file") file: MultipartFile): SettingDto = settingService.addSettings(file).toDto() - - @DeleteMapping("{id}") - fun deleteSetting(@PathVariable id: UUID): Unit = settingService.deleteSetting(id) -} diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/settings/SettingService.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/settings/SettingService.kt deleted file mode 100644 index d11c3e2..0000000 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/settings/SettingService.kt +++ /dev/null @@ -1,75 +0,0 @@ -package de.niklaskerkhoff.tutorassistantappservice.modules.documents.settings - -import com.fasterxml.jackson.databind.ObjectMapper -import de.niklaskerkhoff.tutorassistantappservice.lib.app_components.AppService -import de.niklaskerkhoff.tutorassistantappservice.lib.exceptions.BadRequestException -import de.niklaskerkhoff.tutorassistantappservice.lib.filestore.FileStoreService -import de.niklaskerkhoff.tutorassistantappservice.modules.documents.applications.ApplicationDocumentLoader -import de.niklaskerkhoff.tutorassistantappservice.modules.documents.applications.entities.Document -import de.niklaskerkhoff.tutorassistantappservice.modules.documents.settings.entities.Setting -import de.niklaskerkhoff.tutorassistantappservice.modules.documents.settings.entities.SettingRepo -import de.niklaskerkhoff.tutorassistantappservice.modules.documents.settings.entities.SettingType -import org.springframework.stereotype.Service -import org.springframework.transaction.annotation.Transactional -import org.springframework.web.multipart.MultipartFile -import java.util.* - -@Service -class SettingService( - private val settingRepo: SettingRepo, - private val fileStoreService: FileStoreService, - private val objectMapper: ObjectMapper -) : AppService(), ApplicationDocumentLoader { - - companion object { - private val VALUE_STRATEGIES = mapOf String>( - "plain" to { it }, - "underscored" to { it.replace(" ", "_") } - ) - } - - override fun loadDocuments(): List { - val settings = settingRepo.findAll() - val (tempMainSettings, tempValueSettings) = settings.partition { it.type == SettingType.MAIN } - if (tempMainSettings.isEmpty()) throw BadRequestException("Main setting does not exist") - - val mainJson = tempMainSettings.first().content - val values = tempValueSettings.associate { it.name to it.content.trim().split('\n') } - val fileStoreIdsAndUrls = fileStoreService.listFiles().associate { it.displayName to Pair(it.id, it.storeUrl) } - - return SettingsParser(objectMapper, mainJson, values, fileStoreIdsAndUrls, VALUE_STRATEGIES).parse().also { - log.info("Parsed ${it.size} documents") - } - } - - fun getSettings(): List = settingRepo.findAll() - - @Transactional - fun addSettings(file: MultipartFile): Setting { - val name = file.originalFilename ?: throw BadRequestException("File name must not be null") - val fileEnding = name.split(".").last() - val value = file.inputStream.bufferedReader().use { it.readText() } - val type = if (fileEnding == "json") SettingType.MAIN else SettingType.VALUES - - settingRepo.deleteAllByName(name).also { - log.info("Deleted existing settings with name '$name'") - } - - if (type == SettingType.MAIN) { - settingRepo.deleteAllByType(type).also { - log.info("Deleted main setting if existed") - } - } - - val setting = Setting(name, value, type) - return settingRepo.save(setting).also { - log.info("Saved new setting with name '$name'") - } - } - - fun deleteSetting(id: UUID) { - settingRepo.deleteById(id).also { - log.info("Deleted setting with id $id") - } - } -} diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/settings/SettingsExceptions.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/settings/SettingsExceptions.kt deleted file mode 100644 index 52e22ea..0000000 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/settings/SettingsExceptions.kt +++ /dev/null @@ -1,3 +0,0 @@ -package de.niklaskerkhoff.tutorassistantappservice.modules.documents.settings - -class SettingsParserException(message: String) : Exception(message) diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/settings/SettingsParser.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/settings/SettingsParser.kt deleted file mode 100644 index a538c73..0000000 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/settings/SettingsParser.kt +++ /dev/null @@ -1,189 +0,0 @@ -package de.niklaskerkhoff.tutorassistantappservice.modules.documents.settings - -import com.fasterxml.jackson.databind.JsonNode -import com.fasterxml.jackson.databind.ObjectMapper -import de.niklaskerkhoff.tutorassistantappservice.modules.documents.applications.entities.Document -import de.niklaskerkhoff.tutorassistantappservice.modules.documents.applications.entities.FileDocument -import de.niklaskerkhoff.tutorassistantappservice.modules.documents.applications.entities.WebsiteDocument -import java.util.* - -class SettingsParser( - private val objectMapper: ObjectMapper, - private val mainJson: String, - private val allValues: Map>, - private val fileStoreIdsAndUrls: Map>, - private val valueStrategies: Map String> -) { - companion object { - private const val WEBSITE_TYPE = "Website" - private const val FILE_TYPE = "File" - private val VALUE_REGEX = "\\$\\{(\\w+)}".toRegex() - } - - fun parse(): List { - val json = objectMapper.readTree(mainJson) - - return parseRoot(json) - } - - private fun parseRoot(json: JsonNode): List { - json.requireArray() - - return json.elements().asSequence().map { parseCollectionOrDocument(it) }.flatten().toList() - } - - private fun parseCollectionOrDocument(json: JsonNode): List { - json.requireObject() - - return when { - json.has("title") -> listOf(parseDocument(json, null, null)) - json.has("collection") -> parseCollection(json) - else -> throw SettingsParserException("Failed parsing collection or document") - } - } - - private fun parseCollection(json: JsonNode): List { - json.requireObjectKeys("collection") - - val collection = json["collection"].stringOrThrow() - - return when { - json.has("elements") -> parseElements(json["elements"], collection) - json.has("elementsBuilder") -> parseElementsBuilder(json["elementsBuilder"], collection, json.getValues()) - else -> throw SettingsParserException("Failed parsing collection") - } - } - - private fun parseElements(json: JsonNode, collection: String?): List { - json.requireArray() - - return json.elements().asSequence().map { parseDocument(it, collection, null) }.toList() - } - - private fun parseElementsBuilder( - json: JsonNode, - collection: String?, - values: List - ): List { - json.requireObject() - - return values.map { parseDocument(json, collection, it) } - } - - private fun parseDocument(json: JsonNode, collection: String?, value: String?): Document { - json.requireObjectKeys("type") - - val type = json["type"].stringOrThrow() - - return when (type) { - WEBSITE_TYPE -> parseWebsite(json, collection, value) - FILE_TYPE -> parseFile(json, collection, value) - else -> throw SettingsParserException("Failed parsing document") - } - } - - private fun parseFile(json: JsonNode, collection: String?, value: String?): Document { - json.requireObjectKeys("title", "loaderType", "filename") - - val (fileStoreId, fileStoreUrl) = json.getUrlFromFilename(value) - - return FileDocument( - json["title"].stringWithValueOrThrow(value), - json["loaderType"].stringWithValueOrThrow(value), - collection, - json["isCalendar"].booleanOrFalse(), - fileStoreId, - fileStoreUrl - ) - } - - private fun parseWebsite(json: JsonNode, collection: String?, value: String?): Document { - json.requireObjectKeys("title", "loaderType", "loaderParams") - - return WebsiteDocument( - json["title"].stringWithValueOrThrow(value), - json["loaderType"].stringWithValueOrThrow(value), - collection, - json["isCalendar"].booleanOrFalse(), - parseWebsiteLoaderParams(json["loaderParams"], value) - ) - } - - private fun parseWebsiteLoaderParams(json: JsonNode, value: String?): WebsiteDocument.LoaderParams { - json.requireObjectKeys("url", "htmlSelector", "htmlSelectionIndex") - - return WebsiteDocument.LoaderParams( - json["url"].stringWithValueOrThrow(value), - json["htmlSelector"].stringWithValueOrThrow(value), - json["htmlSelectionIndex"].intOrThrow() - ) - } - - private fun JsonNode?.requireNotNull() { - if (this == null) throw SettingsParserException("Node is null") - } - - private fun JsonNode.requireObject() { - requireNotNull() - if (!isObject) throw SettingsParserException("Node is not an object") - } - - private fun JsonNode.requireArray() { - requireNotNull() - if (!isArray) throw SettingsParserException("Node is not an array") - } - - private fun JsonNode.requireObjectKeys(vararg keys: String) { - requireNotNull() - keys.forEach { if (!has(it)) throw SettingsParserException("Key $it not found in node") } - } - - private fun JsonNode.stringOrThrow(): String { - requireNotNull() - if (!isTextual) throw SettingsParserException("Node is not a string") - return asText() - } - - private fun JsonNode.intOrThrow(): Int { - requireNotNull() - if (!isInt) throw SettingsParserException("Node is not an int") - return asInt() - } - - private fun JsonNode.getValues(): List { - requireNotNull() - requireObjectKeys("values") - - val key = this["values"].stringOrThrow() - - return allValues[key] ?: throw SettingsParserException("Values for key $key not found") - } - - private fun JsonNode.getUrlFromFilename(value: String?): Pair { - requireNotNull() - val filename = this["filename"].stringWithValueOrThrow(value) - val idAndUrl = fileStoreIdsAndUrls[filename] ?: throw SettingsParserException("File $filename does not exist") - val id = idAndUrl.first ?: throw SettingsParserException("File store id must not be null") - return Pair(id, idAndUrl.second) - } - - private fun JsonNode.stringWithValueOrThrow(value: String?): String { - requireNotNull() - val string = stringOrThrow() - if (value == null) return string - - return VALUE_REGEX.replace(string) { matchResult -> - val strategyName = matchResult.groups[1]?.value - ?: throw SettingsParserException("Unknown error reading strategy name") - val strategy = valueStrategies[strategyName] - ?: throw SettingsParserException("Value strategy $strategyName does not exist") - - strategy(value) - } - } - - private fun JsonNode?.booleanOrFalse(): Boolean { - if (this == null) return false - return booleanValue() - } -} diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/settings/entities/Setting.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/settings/entities/Setting.kt deleted file mode 100644 index be5e37e..0000000 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/settings/entities/Setting.kt +++ /dev/null @@ -1,13 +0,0 @@ -package de.niklaskerkhoff.tutorassistantappservice.modules.documents.settings.entities - -import de.niklaskerkhoff.tutorassistantappservice.lib.entities.AppEntity -import jakarta.persistence.Column -import jakarta.persistence.Entity - -@Entity -class Setting( - val name: String, - @Column(columnDefinition = "text") - val content: String, - val type: SettingType -) : AppEntity() diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/settings/entities/SettingDtos.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/settings/entities/SettingDtos.kt deleted file mode 100644 index 3df8324..0000000 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/settings/entities/SettingDtos.kt +++ /dev/null @@ -1,19 +0,0 @@ -package de.niklaskerkhoff.tutorassistantappservice.modules.documents.settings.entities - -import java.util.* - -data class SettingDto( - val id: UUID?, - val name: String, - val content: String, - val type: SettingType, -) { - constructor(setting: Setting) : this( - id = setting.id, - name = setting.name, - content = setting.content, - type = setting.type - ) -} - -fun Setting.toDto() = SettingDto(this) diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/settings/entities/SettingRepo.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/settings/entities/SettingRepo.kt deleted file mode 100644 index 2913b61..0000000 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/settings/entities/SettingRepo.kt +++ /dev/null @@ -1,9 +0,0 @@ -package de.niklaskerkhoff.tutorassistantappservice.modules.documents.settings.entities - -import de.niklaskerkhoff.tutorassistantappservice.lib.entities.AppEntityRepo - -interface SettingRepo : AppEntityRepo { - fun deleteAllByType(type: SettingType) - - fun deleteAllByName(name: String) -} diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/settings/entities/SettingType.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/settings/entities/SettingType.kt deleted file mode 100644 index 8958647..0000000 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/documents/settings/entities/SettingType.kt +++ /dev/null @@ -1,5 +0,0 @@ -package de.niklaskerkhoff.tutorassistantappservice.modules.documents.settings.entities - -enum class SettingType { - MAIN, VALUES -} diff --git a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/helper/HelperController.kt b/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/helper/HelperController.kt deleted file mode 100644 index e7c7cf8..0000000 --- a/tutor-assistant-app-service/src/main/kotlin/de/niklaskerkhoff/tutorassistantappservice/modules/helper/HelperController.kt +++ /dev/null @@ -1,56 +0,0 @@ -package de.niklaskerkhoff.tutorassistantappservice.modules.helper - -import de.niklaskerkhoff.tutorassistantappservice.modules.calendar.entities.CalendarRepo -import org.apache.commons.text.similarity.LevenshteinDistance -import org.springframework.security.access.prepost.PreAuthorize -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RestController - -@RestController -@RequestMapping("helper") -@PreAuthorize("hasRole('document-manager')") -class HelperController( - val calendarRepo: CalendarRepo -) { - - @GetMapping("info-leven") - fun getLeven() { - /*val calendars = calendarRepo.findAll().sortedBy { it.createdDate }.subList(20, 30) - val permutations = calendars.map { calendar1 -> calendars.map { calendar2 -> Pair(calendar1, calendar2) } } - - val sum = permutations.sumOf { row -> - row.sumOf { normalizedLevenshtein(it.first.entries.toString(), it.second.entries.toString()) } - } - - println("---------------------------------------------------") - - println(sum / 100.0) - - println("---------------------------------------------------") - permutations.forEach { row -> - println( - row.joinToString(" ") { - String.format( - "%.5f", - normalizedLevenshtein(it.first.entries.toString(), it.second.entries.toString()) - ) - } - ) - } - println("---------------------------------------------------")*/ - - } - - - private fun normalizedLevenshtein(str1: String, str2: String): Double { - val levenshteinDistance = LevenshteinDistance().apply(str1, str2) - val maxLength = maxOf(str1.length, str2.length) - - return if (maxLength == 0) { - 0.0 - } else { - levenshteinDistance.toDouble() / maxLength - } - } -} diff --git a/tutor-assistant-web/src/common/types.ts b/tutor-assistant-web/src/common/types.ts index 9babeee..079264c 100644 --- a/tutor-assistant-web/src/common/types.ts +++ b/tutor-assistant-web/src/common/types.ts @@ -1,5 +1,8 @@ import { ReactNode } from 'react' +/** + * Props for components, that receive only children or nothing + */ export interface ChildrenProps { children?: ReactNode; } diff --git a/tutor-assistant-web/src/modules/calendar/useCalendar.ts b/tutor-assistant-web/src/modules/calendar/useCalendar.ts index 325352b..5271d44 100644 --- a/tutor-assistant-web/src/modules/calendar/useCalendar.ts +++ b/tutor-assistant-web/src/modules/calendar/useCalendar.ts @@ -33,7 +33,6 @@ export function useCalendar() { async function loadNewCalendar() { await getAuthHttp().post(`${apiBaseUrl}/calendar`) loadCalendarEntries() - console.log('New Info loaded') } return { diff --git a/tutor-assistant-web/src/modules/chat/ChatPage.tsx b/tutor-assistant-web/src/modules/chat/ChatPage.tsx index ba2e947..1d2c273 100644 --- a/tutor-assistant-web/src/modules/chat/ChatPage.tsx +++ b/tutor-assistant-web/src/modules/chat/ChatPage.tsx @@ -4,7 +4,7 @@ import { isNotPresent, isPresent } from '../../common/utils/utils.ts' import { useChatManager } from './hooks/useChatManager.ts' import { useTranslation } from 'react-i18next' import { useSelectedChat } from './hooks/useSelectedChat.ts' -import { useAsyncActionTrigger } from './hooks/useAsyncActionTrigger.ts' +import { useAsyncActionTrigger } from '../../common/hooks/useAsyncActionTrigger.ts' import { MainContent, Row, VStack } from '../../common/components/containers/flex-layout.tsx' import { Divider } from '@mui/joy' import { ChatDetails } from './components/details/ChatDetails.tsx' @@ -12,6 +12,11 @@ import { ChatOverview } from './components/overview/ChatOverview.tsx' import { SubmitTextarea } from '../../common/components/widgets/SubmitTextarea.tsx' +/** + * Shows either ChatOverview or ChatDetails together with a Textarea for sending messages. + * In case of ChatDetails sending messages adds the message to the opened chat. + * In case of ChatOverview sending messages leads to creating a new chat with this message and opening it in ChatDetails. + */ export function ChatPage() { const { t } = useTranslation() const navigate = useNavigate() @@ -21,7 +26,6 @@ export function ChatPage() { const chatId = useParams().chatId const { createChat } = useChatManager() const { selectedChat, sendMessage, isLoading } = useSelectedChat(chatId) - console.log('selectedChat', selectedChat) const [isSending, sendMessageAction] = useAsyncActionTrigger( handleSend, () => isPresent(selectedChat) && selectedChat.id === chatId, diff --git a/tutor-assistant-web/src/modules/chat/ChatProvider.tsx b/tutor-assistant-web/src/modules/chat/ChatProvider.tsx index 8f7c938..c0be2bb 100644 --- a/tutor-assistant-web/src/modules/chat/ChatProvider.tsx +++ b/tutor-assistant-web/src/modules/chat/ChatProvider.tsx @@ -14,6 +14,16 @@ type ChatContextType = { setSelectedMessageFeedback: Dispatch> } + +/** + * Use only through useChatContext. + * + * Provides states and setState for + * * chats for managing all chats + * * selectedChat for managing a selected chat + * * selectedMessageId for managing a selected message + * * selectedMessageFeedback for managing the feedback of a selected message + */ export const ChatContext = createContext({ chats: [], setChats: chill, @@ -25,6 +35,9 @@ export const ChatContext = createContext({ setSelectedMessageFeedback: chill, }) +/** + * Applies the ChatContext + */ export function ChatProvider({ children }: ChildrenProps) { const [chats, setChats] = useState([]) const [selectedChat, setSelectedChat] = useState() diff --git a/tutor-assistant-web/src/modules/chat/chat-model.ts b/tutor-assistant-web/src/modules/chat/chat-model.ts index bea0dec..501576c 100644 --- a/tutor-assistant-web/src/modules/chat/chat-model.ts +++ b/tutor-assistant-web/src/modules/chat/chat-model.ts @@ -1,9 +1,15 @@ +/** + * Chat between user and assistant. + */ export interface Chat { id: string summary?: ChatSummary messages: ChatMessage[] } +/** + * Messages in a chat. Contexts are only present if role is ai. + */ export interface ChatMessage { id?: string role: 'user' | 'ai' @@ -11,21 +17,29 @@ export interface ChatMessage { contexts?: ChatMessageContext[] } +/** + * Summary of the chat based on the messages. + */ export interface ChatSummary { title: string, subtitle: string, content: string } +/** + * Context used for generating an ai message. + */ export interface ChatMessageContext { title?: string originalKey?: string - heading?: string page?: number content?: string score?: number } +/** + * Feedback given by a user to an ai message. + */ export interface ChatMessageFeedback { rating: number content: string diff --git a/tutor-assistant-web/src/modules/chat/components/details/ChatAdditionalInfo.tsx b/tutor-assistant-web/src/modules/chat/components/details/ChatAdditionalInfo.tsx index b36322f..8539d2d 100644 --- a/tutor-assistant-web/src/modules/chat/components/details/ChatAdditionalInfo.tsx +++ b/tutor-assistant-web/src/modules/chat/components/details/ChatAdditionalInfo.tsx @@ -1,23 +1,22 @@ -import { Chat, ChatMessageContext, ChatSummary } from '../../chat-model.ts' +import { Chat } from '../../chat-model.ts' import React, { useMemo, useState } from 'react' import { Bar } from '../../../../common/components/containers/Bar.tsx' import { isNotPresent, isPresent } from '../../../../common/utils/utils.ts' import { Header } from '../../../../common/components/containers/Header.tsx' import { useTranslation } from 'react-i18next' -import { Scroller } from '../../../../common/components/containers/Scroller.tsx' -import { MainContent, Spacer, VStack } from '../../../../common/components/containers/flex-layout.tsx' -import { Box, Button, Card, CardActions, CardContent, ToggleButtonGroup, Typography } from '@mui/joy' +import { Button, ToggleButtonGroup } from '@mui/joy' import { empty } from '../../../../common/utils/array-utils.ts' -import { StyledMarkdown } from '../../../../common/components/widgets/StyledMarkdown.tsx' -import { useOpenContexts } from '../../hooks/useOpenContexts.ts' -import { Multiline } from '../../../../common/components/widgets/Multiline.tsx' -import { roundTo } from '../../../../common/utils/math-utils.ts' +import { ChatSummary } from './ChatSummary.tsx' +import { ChatContexts } from './ChatContexts.tsx' interface Props { chat: Chat selectedMessageId?: string } +/** + * Right sidebar for displaying either the contexts (sources) or the summary of the selected message. + */ export function ChatAdditionalInfo({ chat, selectedMessageId }: Props) { const { t } = useTranslation() const [additionalInfo, setAdditionalInfo] = useState<'summary' | 'contexts' | null>('contexts') @@ -34,122 +33,37 @@ export function ChatAdditionalInfo({ chat, selectedMessageId }: Props) { if (isNotPresent(additionalInfo)) return <> - return -
- - - - } - /> - { - additionalInfo === 'summary' && - } - - { - additionalInfo === 'contexts' && +
+ + + + } /> - } - -} - -interface SummaryProps { - summary: ChatSummary | undefined -} - -function Summary({ summary }: SummaryProps) { + { + additionalInfo === 'summary' && + } - return ( - <> - - - { - isPresent(summary) && ( - - {`## ${summary.title}`} - {`### ${summary.subtitle}`} - {summary.content} - - ) - } - - - + { + additionalInfo === 'contexts' && + } + ) } -interface ContextsProps { - contexts: ChatMessageContext[] | undefined -} - -function Contexts({ contexts }: ContextsProps) { - const { t } = useTranslation() - - const { openContexts } = useOpenContexts() - - if (isNotPresent(contexts)) contexts = [] - - function getTitleAndPage(context: ChatMessageContext) { - const pageOutput = isPresent(context.page) ? `, ${t('Page')} ${context.page + 1}` : '' - return isPresent(context.title) ? `${context.title}${pageOutput}` : '' - } - - if (contexts.length === 0) return ( - - - {t('Select a message')} - - - ) - return ( - <> - - - - { - contexts.map((context, index) => ( - - - - - {getTitleAndPage(context)} - - - {t('Relevance')}: {roundTo(context.score ?? -1, 2)} - - - - - - {isPresent(context.originalKey) && ( - - - - - )} - - )) - } - - - - - - - ) -} diff --git a/tutor-assistant-web/src/modules/chat/components/details/ChatDetails.tsx b/tutor-assistant-web/src/modules/chat/components/details/ChatDetails.tsx index bd0de3b..670ea4d 100644 --- a/tutor-assistant-web/src/modules/chat/components/details/ChatDetails.tsx +++ b/tutor-assistant-web/src/modules/chat/components/details/ChatDetails.tsx @@ -12,16 +12,18 @@ import { last } from '../../../../common/utils/array-utils.ts' import { ChatAdditionalInfo } from './ChatAdditionalInfo.tsx' import { useChatContext } from '../../useChatContext.ts' - +/** + * Displays an opened chat with all the messages and the additional information. + * @see ChatMessageList + * @see ChatAdditionalInfo + */ export function ChatDetails() { const { t } = useTranslation() const navigate = useNavigate() - const { selectedChat, selectedMessageId, setSelectedMessageId } = useChatContext() useEffect(() => { - console.log('hier', selectedChat) if (isNotPresent(selectedChat)) return setSelectedMessageId(last(selectedChat.messages)?.id) }, [selectedChat?.messages.length]) diff --git a/tutor-assistant-web/src/modules/chat/components/details/ChatMessageItem.tsx b/tutor-assistant-web/src/modules/chat/components/details/ChatMessageItem.tsx index 3e6bd55..31fe7c5 100644 --- a/tutor-assistant-web/src/modules/chat/components/details/ChatMessageItem.tsx +++ b/tutor-assistant-web/src/modules/chat/components/details/ChatMessageItem.tsx @@ -20,6 +20,15 @@ interface Props { } +/** + * Displays a message in a chat. + * Renders ai messages as markdown. + * Shows feedback input for selected ai messages. + * + * @param message to be displayed. + * @param onMessageClick callback when a message is clicked. + * @param selectedMessageId id of the selected message. + */ export function ChatMessageItem({ message, onMessageClick, selectedMessageId }: Props) { const { t } = useTranslation() diff --git a/tutor-assistant-web/src/modules/chat/components/details/ChatMessageList.tsx b/tutor-assistant-web/src/modules/chat/components/details/ChatMessageList.tsx index 6bb3b78..9cce83e 100644 --- a/tutor-assistant-web/src/modules/chat/components/details/ChatMessageList.tsx +++ b/tutor-assistant-web/src/modules/chat/components/details/ChatMessageList.tsx @@ -8,6 +8,14 @@ interface Props { selectedMessageId?: string } + +/** + * Displays all messages as ChatMessageItems in a VStack. + * + * @param messages to be displayed. + * @param onMessageClick callback when a message is clicked. + * @param selectedMessageId id of the selected message. + */ export function ChatMessageList({ messages, onMessageClick, selectedMessageId }: Props) { return ( diff --git a/tutor-assistant-web/src/modules/chat/components/overview/ChatOverview.tsx b/tutor-assistant-web/src/modules/chat/components/overview/ChatOverview.tsx index e01e1e9..a11cbdb 100644 --- a/tutor-assistant-web/src/modules/chat/components/overview/ChatOverview.tsx +++ b/tutor-assistant-web/src/modules/chat/components/overview/ChatOverview.tsx @@ -9,6 +9,10 @@ import { ChatCard } from './ChatOverviewCard.tsx' import { Button } from '@mui/joy' import { useNavigate } from 'react-router-dom' + +/** + * Displays overview page content. Displays a chat card for each chat. + */ export function ChatOverview() { const { t } = useTranslation() diff --git a/tutor-assistant-web/src/modules/chat/components/overview/ChatOverviewCard.tsx b/tutor-assistant-web/src/modules/chat/components/overview/ChatOverviewCard.tsx index 99f87b1..2bc0d1f 100644 --- a/tutor-assistant-web/src/modules/chat/components/overview/ChatOverviewCard.tsx +++ b/tutor-assistant-web/src/modules/chat/components/overview/ChatOverviewCard.tsx @@ -22,7 +22,14 @@ interface Props { deleteChat: (chatId: string) => void, } - +/** + * Displays a chat card for each chat. + * Displays the summary, menu with delete option and button for opening the chat in ChatDetails. + * + * @param chat to be displayed. + * @param deleteChat function for deleting a chat. + * @constructor + */ export function ChatCard({ chat, deleteChat }: Props) { const { t } = useTranslation() const navigate = useNavigate() diff --git a/tutor-assistant-web/src/modules/chat/hooks/useAsyncActionTrigger.ts b/tutor-assistant-web/src/modules/chat/hooks/useAsyncActionTrigger.ts deleted file mode 100644 index 61527e8..0000000 --- a/tutor-assistant-web/src/modules/chat/hooks/useAsyncActionTrigger.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { useEffect, useState } from 'react' - -export function useAsyncActionTrigger( - action: () => Promise, - canPerformAction: () => boolean, - triggers: unknown[], -): [boolean, () => void] { - const [isProcessing, setIsProcessing] = useState(false) - - function process() { - setIsProcessing(true) - } - - useEffect(() => { - if (isProcessing && canPerformAction()) { - doAction() - } - }, [...triggers, isProcessing]) - - async function doAction() { - await action() - setIsProcessing(false) - } - - return [ - isProcessing, - process, - ] -} diff --git a/tutor-assistant-web/src/modules/chat/hooks/useChatManager.ts b/tutor-assistant-web/src/modules/chat/hooks/useChatManager.ts index 26b1030..d5c4b41 100644 --- a/tutor-assistant-web/src/modules/chat/hooks/useChatManager.ts +++ b/tutor-assistant-web/src/modules/chat/hooks/useChatManager.ts @@ -6,6 +6,15 @@ import { remove } from '../../../common/utils/array-utils.ts' import { isPresent } from '../../../common/utils/utils.ts' import { useChatContext } from '../useChatContext.ts' + +/** + * Manages all chats. + * + * @returns + * * summarizedChats: all chats having a summary the user. + * * createChat(): create a new empty chat. + * * deleteChat(): deletes a chat. + */ export function useChatManager() { const { chats, setChats } = useChatContext() @@ -34,7 +43,6 @@ export function useChatManager() { } return { - chats, summarizedChats, createChat, deleteChat, diff --git a/tutor-assistant-web/src/modules/chat/hooks/useChatMessageFeedback.ts b/tutor-assistant-web/src/modules/chat/hooks/useChatMessageFeedback.ts index 39e9ac2..844830d 100644 --- a/tutor-assistant-web/src/modules/chat/hooks/useChatMessageFeedback.ts +++ b/tutor-assistant-web/src/modules/chat/hooks/useChatMessageFeedback.ts @@ -4,6 +4,17 @@ import { ChatMessageFeedback } from '../chat-model.ts' import { isNotPresent } from '../../../common/utils/utils.ts' import { useChatContext } from '../useChatContext.ts' + +/** + * Manages feedback to ai messages. + * + * @returns + * * selectedMessageFeedback: the feedback of the selected message. + * * setContent: state setter for the content of the message. + * * loadFeedback(messageId: string): for loading the feedback. + * * updateFeedbackRating: sends the update of the rating of the feedback to the server. + * * updateFeedbackContent: sends the update of the content of the feedback to the server. + */ export function useChatMessageFeedback() { const { getAuthHttp } = useAuth() @@ -24,7 +35,6 @@ export function useChatMessageFeedback() { async function updateFeedbackContent(messageId: string, content: string) { if (isNotPresent(messageId)) return - console.log(content) const response = await getAuthHttp() .patch(`${apiBaseUrl}/chats/messages/${messageId}/feedback-content`, { content }) setContent(response.data.content) diff --git a/tutor-assistant-web/src/modules/chat/hooks/useFiles.ts b/tutor-assistant-web/src/modules/chat/hooks/useFiles.ts deleted file mode 100644 index 49757b9..0000000 --- a/tutor-assistant-web/src/modules/chat/hooks/useFiles.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { useAuth } from '../../../app/auth/useAuth.ts' -import { apiBaseUrl } from '../../../app/base.ts' - -export function useFiles() { - const { getAuthHttp } = useAuth() - - async function loadFile(id: string) { - const response = await getAuthHttp().get(`${apiBaseUrl}/documents/resources/${id}`, { - responseType: 'blob', - }) - - const blobUrl = URL.createObjectURL(response.data) - window.open(blobUrl, '_blank') - - setTimeout(() => URL.revokeObjectURL(blobUrl), 30_000) - } - - return { - loadFile, - } -} \ No newline at end of file diff --git a/tutor-assistant-web/src/modules/chat/hooks/useOpenContexts.ts b/tutor-assistant-web/src/modules/chat/hooks/useOpenContexts.ts index b578e49..bebb5c3 100644 --- a/tutor-assistant-web/src/modules/chat/hooks/useOpenContexts.ts +++ b/tutor-assistant-web/src/modules/chat/hooks/useOpenContexts.ts @@ -1,9 +1,14 @@ -import { useFiles } from './useFiles.ts' +import { useFileResources } from './useFileResources.ts' import { ChatMessageContext } from '../chat-model.ts' import { isNotPresent } from '../../../common/utils/utils.ts' + +/** + * Opens a context from a website-resource or file-resource inside the browser. + * @see useFileResources + */ export function useOpenContexts() { - const { loadFile } = useFiles() + const { loadFile } = useFileResources() function openContexts(context: ChatMessageContext | string | undefined) { if (isNotPresent(context)) return diff --git a/tutor-assistant-web/src/modules/chat/hooks/useSelectedChat.ts b/tutor-assistant-web/src/modules/chat/hooks/useSelectedChat.ts index 293059c..eba8c47 100644 --- a/tutor-assistant-web/src/modules/chat/hooks/useSelectedChat.ts +++ b/tutor-assistant-web/src/modules/chat/hooks/useSelectedChat.ts @@ -8,6 +8,19 @@ import { useKeycloak } from '@react-keycloak/web' import { isNotPresent } from '../../../common/utils/utils.ts' import { useChatContext } from '../useChatContext.ts' + +/** + * Loads and provides the selected chat. + * Handles message sending and response message loading. + * Loads response messages are loaded from an event stream. + * + * @returns + * * selectedChat: the selected chat. + * * sendMessage(message: string): function for sending messages. + * * isLoading: true while sending new message and waiting for response to finish loading. false otherwise. + * + * @param chatId of the selected chat. Undefined if no chat is selected. + */ export function useSelectedChat(chatId: string | undefined) { const eventStreamEnd = '=====END=====' const messageEnd = '=====MESSAGE_END=====' @@ -21,10 +34,7 @@ export function useSelectedChat(chatId: string | undefined) { const { keycloak } = useKeycloak() - console.log('setSelectedChat', setSelectedChat) - console.log(chatId) useEffect(() => { - console.log('loadChat') loadChat() return () => { cleanupStreaming() @@ -39,7 +49,6 @@ export function useSelectedChat(chatId: string | undefined) { } setIsLoading(true) const result = await getAuthHttp().get(`${apiBaseUrl}/chats/${chatId}`) - console.log('result.data', result.data) setSelectedChat(result.data) setIsLoading(false) } diff --git a/tutor-assistant-web/src/modules/chat/useChatContext.ts b/tutor-assistant-web/src/modules/chat/useChatContext.ts index b763084..d026238 100644 --- a/tutor-assistant-web/src/modules/chat/useChatContext.ts +++ b/tutor-assistant-web/src/modules/chat/useChatContext.ts @@ -1,6 +1,10 @@ import { useContext } from 'react' import { ChatContext } from './ChatProvider.tsx' + +/** + * Provides states for managing chats, messages and feedbacks on messages. + */ export function useChatContext() { return useContext(ChatContext) } diff --git a/tutor-assistant-web/src/modules/documents/DocumentsPage.tsx b/tutor-assistant-web/src/modules/documents/DocumentsPage.tsx index ed74252..010f904 100644 --- a/tutor-assistant-web/src/modules/documents/DocumentsPage.tsx +++ b/tutor-assistant-web/src/modules/documents/DocumentsPage.tsx @@ -2,17 +2,17 @@ import { HStack, MainContent, VStack } from '../../common/components/containers/ import { Header } from '../../common/components/containers/Header.tsx' import { useTranslation } from 'react-i18next' import { useNavigate } from 'react-router-dom' -import { Button } from '@mui/joy' +import { IconButton } from '@mui/joy' import { ArrowBackIosNew } from '@mui/icons-material' import React from 'react' import { DocumentSettingsList } from './components/DocumentSettingsList.tsx' -import { TutorAssistantDocumentsList } from './components/TutorAssistantDocumentsList.tsx' +import { DocumentsList } from './components/DocumentsList.tsx' import { useAuth } from '../../app/auth/useAuth.ts' /** * View and manage documents. - * This includes uploading settings and perform indexing based on these settings. + * This includes uploading settings and perform embedding based on these settings. */ export function DocumentsPage() { const navigate = useNavigate() @@ -31,19 +31,14 @@ export function DocumentsPage() {
navigate('/chats')} - startDecorator={} - > - {t('Chats')} - + navigate('/chats')}> + + } title={t('Documents')} /> - + diff --git a/tutor-assistant-web/src/modules/documents/components/TutorAssistantDocumentsList.tsx b/tutor-assistant-web/src/modules/documents/components/TutorAssistantDocumentsList.tsx deleted file mode 100644 index 264efc4..0000000 --- a/tutor-assistant-web/src/modules/documents/components/TutorAssistantDocumentsList.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { useTranslation } from 'react-i18next' -import { useTutorAssistantDocuments } from '../hooks/useTutorAssistantDocuments.ts' -import { Row, Spacer, VStack } from '../../../common/components/containers/flex-layout.tsx' -import { Accordion, AccordionDetails, AccordionGroup, AccordionSummary, Button, Typography } from '@mui/joy' -import { StandardList } from './StandardList.tsx' -import { Scroller } from '../../../common/components/containers/Scroller.tsx' -import { useOpenContexts } from '../../chat/hooks/useOpenContexts.ts' - -interface Props { - canManage: boolean -} - -/** - * Renders a list and buttons for viewing and managing file and website documents. - * - * @param canManage true if the user can index, reindex and delete, false if the user can only view. - */ -export function TutorAssistantDocumentsList({ canManage }: Props) { - const { t } = useTranslation() - const { - index, - groupedDocuments, - reindexFile, - reindexWebsite, - deleteFile, - deleteWebsite, - } = useTutorAssistantDocuments() - - const { openContexts } = useOpenContexts() - - return ( - - - - - - - {canManage && ()} - - - - { - Object.keys(groupedDocuments).map((key, index) => ( - - - {key} - - - file.title} - onClick={file => openContexts(file.fileStoreId)} - onReload={file => reindexFile(file.id)} - onDelete={file => deleteFile(file.id)} - canManage={canManage} - /> - website.title} - onClick={website => openContexts(website.url)} - onReload={website => reindexWebsite(website.id)} - onDelete={website => deleteWebsite(website.id)} - canManage={canManage} - /> - - - - )) - } - - - - - - - - - ) -} diff --git a/tutor-assistant-web/src/modules/documents/hooks/useDocumentSettings.tsx b/tutor-assistant-web/src/modules/documents/hooks/useDocumentSettings.tsx index 75dc9e4..4a96b1a 100644 --- a/tutor-assistant-web/src/modules/documents/hooks/useDocumentSettings.tsx +++ b/tutor-assistant-web/src/modules/documents/hooks/useDocumentSettings.tsx @@ -34,12 +34,12 @@ export function useDocumentSettings() { }, []) async function loadSettings() { - const response = await getAuthHttp().get(`${apiBaseUrl}/documents/settings`) + const response = await getAuthHttp().get(`${apiBaseUrl}/embedding_manager/settings`) setSettings(response.data) } async function loadResources() { - const response = await getAuthHttp().get(`${apiBaseUrl}/documents/resources`) + const response = await getAuthHttp().get(`${apiBaseUrl}/embedding_manager/resources`) setResources(response.data) } @@ -59,12 +59,12 @@ export function useDocumentSettings() { } async function deleteSetting(id: string) { - await getAuthHttp().delete(`${apiBaseUrl}/documents/settings/${id}`) + await getAuthHttp().delete(`${apiBaseUrl}/embedding_manager/settings/${id}`) setSettings(prevState => remove(id, prevState)) } async function deleteResource(id: string) { - await getAuthHttp().delete(`${apiBaseUrl}/documents/resources/${id}`) + await getAuthHttp().delete(`${apiBaseUrl}/embedding_manager/resources/${id}`) setResources(prevState => remove(id, prevState)) } @@ -74,7 +74,7 @@ export function useDocumentSettings() { formData.append('file', file) try { - return await getAuthHttp().post(`${apiBaseUrl}/documents/${pathEnding}`, formData, { + return await getAuthHttp().post(`${apiBaseUrl}/embedding_manager/${pathEnding}`, formData, { headers: { 'Content-Type': 'multipart/form-data', }, diff --git a/tutor-assistant-web/src/modules/documents/hooks/useTutorAssistantDocuments.ts b/tutor-assistant-web/src/modules/documents/hooks/useTutorAssistantDocuments.ts deleted file mode 100644 index 325117e..0000000 --- a/tutor-assistant-web/src/modules/documents/hooks/useTutorAssistantDocuments.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { useEffect, useMemo, useState } from 'react' -import { useAuth } from '../../../app/auth/useAuth.ts' -import { apiBaseUrl } from '../../../app/base.ts' -import { remove } from '../../../common/utils/array-utils.ts' -import { isNotPresent } from '../../../common/utils/utils.ts' -import { FileDocument, WebsiteDocument } from '../model.ts' -import { useTranslation } from 'react-i18next' - - -/** - * Manages file and website documents. - * - * @property index function to run the indexing process. - * @property groupedDocuments file and website documents grouped by their collection. - * Each collection contains its file and website documents. - * @property websites website documents. - * @property reindexFile function to reindex a file. - * @property reindexWebsite function to reindex a website. - * @property deleteFile function to delete a file. - * @property deleteWebsite function to delete a website. - */ -export function useTutorAssistantDocuments() { - const { t } = useTranslation() - const { getAuthHttp } = useAuth() - - const [files, setFiles] = useState([]) - const [websites, setWebsites] = useState([]) - - const generalKey = t('General') - - const groupedDocuments = useMemo(() => { - const result = { - [generalKey]: { - files: [] as FileDocument[], - websites: [] as WebsiteDocument[], - }, - } - - files.forEach((file) => { - const key = file.collection ?? generalKey - if (!(key in result)) { - result[key] = { files: [], websites: [] } - } - result[key].files.push(file) - }) - - websites.forEach((website) => { - const key = website.collection ?? generalKey - if (!(key in result)) { - result[key] = { files: [], websites: [] } - } - result[key].websites.push(website) - }) - return result - }, [files, websites]) - - - useEffect(() => { - loadFiles() - loadWebsites() - }, []) - - async function index() { - await getAuthHttp().post(`${apiBaseUrl}/documents/applications/index`) - loadFiles() - loadWebsites() - } - - - async function loadFiles() { - const response = await getAuthHttp().get(`${apiBaseUrl}/documents/applications/files`) - setFiles(response.data) - } - - async function loadWebsites() { - const response = await getAuthHttp().get(`${apiBaseUrl}/documents/applications/websites`) - setWebsites(response.data) - } - - async function reindexFile(id: string | undefined) { - if (isNotPresent(id)) return - await getAuthHttp().post(`${apiBaseUrl}/documents/applications/files/${id}/reindex`) - } - - async function reindexWebsite(id: string | undefined) { - if (isNotPresent(id)) return - await getAuthHttp().post(`${apiBaseUrl}/documents/applications/websites/${id}/reindex`) - } - - async function deleteFile(id: string | undefined) { - if (isNotPresent(id)) return - await getAuthHttp().delete(`${apiBaseUrl}/documents/applications/files/${id}`) - setFiles(prevState => remove(id, prevState)) - } - - async function deleteWebsite(id: string | undefined) { - if (isNotPresent(id)) return - await getAuthHttp().delete(`${apiBaseUrl}/documents/applications/websites/${id}`) - setWebsites(prevState => remove(id, prevState)) - } - - return { - index, - groupedDocuments, - reindexFile, - reindexWebsite, - deleteFile, - deleteWebsite, - } -} diff --git a/tutor-assistant-web/src/modules/documents/model.ts b/tutor-assistant-web/src/modules/documents/model.ts index c2ffd98..812ddb0 100644 --- a/tutor-assistant-web/src/modules/documents/model.ts +++ b/tutor-assistant-web/src/modules/documents/model.ts @@ -25,14 +25,14 @@ export interface Resource { } /** - * Indexed document + * Embedded document * * @property id unique string * @property title human readable string, must not be unique * @property loaderType specifying how the document is loaded * @property collection for grouping */ -export interface TutorAssistantDocument { +export interface RagDocument { id: string title: string loaderType: string @@ -40,19 +40,19 @@ export interface TutorAssistantDocument { } /** - * Indexed file document + * Embedded file document * * @property fileStoreId unique string from the file store */ -export interface FileDocument extends TutorAssistantDocument { +export interface FileDocument extends RagDocument { fileStoreId: string } /** - * Indexed website document + * Embedded website document * * @property url of the website */ -export interface WebsiteDocument extends TutorAssistantDocument { +export interface WebsiteDocument extends RagDocument { url: string } \ No newline at end of file diff --git a/tutor-assistant-web/src/texts/texts.json b/tutor-assistant-web/src/texts/texts.json index 2ef5bab..86edebc 100644 --- a/tutor-assistant-web/src/texts/texts.json +++ b/tutor-assistant-web/src/texts/texts.json @@ -19,6 +19,10 @@ "en": "Documents", "de": "Dokumente" }, + "Embed": { + "en": "Embed", + "de": "Einbetten" + }, "Feedback": { "en": "Feedback", "de": "Feedback" @@ -35,10 +39,6 @@ "en": "General", "de": "Allgemein" }, - "Index": { - "en": "Index", - "de": "Indizieren" - }, "Logout": { "en": "Logout", "de": "Abmelden" diff --git a/tutor-assistant/main.py b/tutor-assistant/main.py index 53e3805..940f887 100644 --- a/tutor-assistant/main.py +++ b/tutor-assistant/main.py @@ -3,7 +3,7 @@ import uvicorn from fastapi import FastAPI -from tutor_assistant.controller.api import chats_controller, documents_controller, calendar_controller, demo_controller +from tutor_assistant.controller.api import chats_api, documents_api, calendar_api from tutor_assistant.controller.config.domain_config import config _app = FastAPI( @@ -16,11 +16,9 @@ def _main(): config.vector_store_manager.create_if_not_exists() - # app.logger = config.logger - _app.include_router(calendar_controller.router, tags=["calendar"]) - _app.include_router(chats_controller.router, tags=["chats"]) - _app.include_router(demo_controller.router, tags=["demo"]) - _app.include_router(documents_controller.router, tags=["documents"]) + _app.include_router(calendar_api.router, tags=["calendar"]) + _app.include_router(chats_api.router, tags=["chats"]) + _app.include_router(documents_api.router, tags=["documents"]) uvicorn.run(_app, host=os.getenv('HOST'), port=8500) diff --git a/tutor-assistant/resources/prompt_templates/hybrid_retriever/multiple_retriever_queries.txt b/tutor-assistant/resources/prompt_templates/hybrid_retriever/multiple_retriever_queries.txt deleted file mode 100644 index e69de29..0000000 diff --git a/tutor-assistant/resources/prompt_templates/hybrid_retriever/multiple_retriever_queries_1.txt b/tutor-assistant/resources/prompt_templates/hybrid_retriever/multiple_retriever_queries_1.txt deleted file mode 100644 index 1631f6f..0000000 --- a/tutor-assistant/resources/prompt_templates/hybrid_retriever/multiple_retriever_queries_1.txt +++ /dev/null @@ -1,5 +0,0 @@ -Dies ist ein Chatverlauf. -Untersuche, welche groben Themen in der letzten Nachricht des Benutzers vorkommen. Benenne sie jeweils mit einem Begriff. -Gib möglichst wenig Begriffe aus. Sie sollen nur die Hauptthemen der Anfragen abdecken. Häufig wird nur ein Begriff notwendig sein. -Verwende die anderen Nachrichten nur, um den Kontext der letzten Nachricht zu verstehen. -Ganz wichtig: Beantworte nicht die Frage. Gib nur die Begriffe zu den groben Themen aus. Trenne sie mit Semikolons. diff --git a/tutor-assistant/resources/prompt_templates/hybrid_retriever/multiple_retriever_queries_2.txt b/tutor-assistant/resources/prompt_templates/hybrid_retriever/multiple_retriever_queries_2.txt deleted file mode 100644 index a66b07c..0000000 --- a/tutor-assistant/resources/prompt_templates/hybrid_retriever/multiple_retriever_queries_2.txt +++ /dev/null @@ -1,5 +0,0 @@ -Dies ist ein Chatverlauf. -Ich muss Anfragen an einen Vectorstore machen, sodass die richtigen Dokumente abgerufen werden, sodass die letzte Frage des Benutzers beantwortet werden kann. -Gib mir Anfragen, die ich stellen soll. Trenne die Anfragen mit einem Semikolon. -Gib mir möglichst wenig Anfragen, um Zeit zu sparen. Die Anfragen sollen dennoch alles Wichtige abdecken. In vielen Fällen wird eine Anfrage reichen. -Ganz wichtig: beantworte nicht die Frage, sondern gib nur die Anfragen aus. diff --git a/tutor-assistant/resources/prompt_templates/hybrid_retriever/multiple_retriever_queries_3.txt b/tutor-assistant/resources/prompt_templates/hybrid_retriever/multiple_retriever_queries_3.txt deleted file mode 100644 index 37b52d6..0000000 --- a/tutor-assistant/resources/prompt_templates/hybrid_retriever/multiple_retriever_queries_3.txt +++ /dev/null @@ -1,21 +0,0 @@ -Dies ist ein Chatverlauf. Es soll eine Antwort auf die letzte Nachricht des Benutzers erstellt werden. -Um eine Antwort zu generieren, müssen erstmal die notwendigen Dokumente aus einem Vectorstore abgerufen werden. - -Ich möchte, dass du mir Anfragen ausgibst, die ich an den Vectorstore senden kann. Beachte dabei folgendes: -- Die Anfragen sollen beschreiben, was der Benutzer möchte. Zudem sollen sie so formuliert sein, dass bei einer Ähnlichkeitssuche die richtigen Dokumente übereinstimmen. -- Überlege dir, welche Informationen du bräuchtest und formuliere dahingehend Anfragen. -- Es soll die letzte Nachricht des Benutzers beantwortet werden. Beziehe jedoch den Chat-Verlauf mit ein, wenn es für den Kontext wichtig ist. -- Es sollen möglichst wenig Anfragen generiert werden. Je mehr Anfragen, desto länger muss der Benutzer warten. -- Die Anfragen sollen sehr verschieden sein. Bei ähnlichen Anfragen kämen dieselben Dokumente zurück, damit gäbe es Redundanz. -- Verwende unter keinen Umständen dieselben Begriffe in mehreren Anfragen. -- Die Anfragen sollen Dokumente identifizieren, nicht die Frage beantworten. -- Häufig wird nur eine Anfrage benötigt. -- Formuliere nur kurze Anfragen. Sie sollen nur wenige Wörter lang sein, wenn überhaupt mehr als ein Wort. -- Versuche wirklich den Kern der Frage des Benutzers zu erfassen und entsprechende Anfragen zu generieren. Gib das Thema aus und nicht, was die Frage dazu ist. -- Benutzer die Begriffe des Benutzers. -- Trenne die Anfragen mit einem Semikolon - -Es ist wirklich super wichtig, dass du nur ganz wenige Anfragen ausgibst. Gib wirklich nur dann mehrere aus, wenn es absolut notwendig ist, um den Kontext der Nachricht zu erfassen. Meistens ist dies nicht der Fall!!! -Verwende auf gar keinen Fall dasselbe Wort in mehreren Anfragen. Das bedeutet, dass man nur eine Anfrage braucht!!! - -Ganz wichtig: beantworte nicht die Frage, sondern gib nur die Anfragen aus. diff --git a/tutor-assistant/tutor_assistant/controller/api/calendar_controller.py b/tutor-assistant/tutor_assistant/controller/api/calendar_controller.py deleted file mode 100644 index 655ce81..0000000 --- a/tutor-assistant/tutor_assistant/controller/api/calendar_controller.py +++ /dev/null @@ -1,36 +0,0 @@ -import json - -import numpy as np -from fastapi import APIRouter -from langchain_core.documents import Document - -from tutor_assistant.controller.config.domain_config import config -from tutor_assistant.controller.utils.data_transfer_utils import json_output -from tutor_assistant.domain.calendar.calendar_chain_service import CalendarChainService -from tutor_assistant.utils.string_utils import shorten_middle - -router = APIRouter() - - -@router.post('/calendar') -async def _calendar(): - config.logger.info('POST /calendar') - - chain = CalendarChainService(config).create() - - result = chain.invoke({}) - context = result['context'] - answer = result['answer'] - - entry: Document - json_context = json.dumps([json.loads(json.dumps(entry.to_json(), cls=NumpyEncoder)) for entry in context]) - config.logger.info(f'Result: {shorten_middle(answer, 30)}') - - return json_output(answer) - - -class NumpyEncoder(json.JSONEncoder): - def default(self, obj): - if isinstance(obj, np.float32): - return float(obj) - return super().default(obj) diff --git a/tutor-assistant/tutor_assistant/controller/api/chats_controller.py b/tutor-assistant/tutor_assistant/controller/api/chats_controller.py deleted file mode 100644 index 1ab407f..0000000 --- a/tutor-assistant/tutor_assistant/controller/api/chats_controller.py +++ /dev/null @@ -1,50 +0,0 @@ -import json - -from fastapi import Request, APIRouter -from starlette.responses import StreamingResponse - -from tutor_assistant.controller.config.domain_config import config -from tutor_assistant.controller.utils.api_utils import check_request_body -from tutor_assistant.controller.utils.data_transfer_utils import json_output -from tutor_assistant.controller.utils.langchain_utils import stream_chain, stream_response -from tutor_assistant.domain.chats.message_chain_service import MessageChainService -from tutor_assistant.domain.chats.message_multi_steps_chain_service import MessageMultiStepsChainService -from tutor_assistant.domain.chats.summary_chain_service import SummaryChainService -from tutor_assistant.utils.string_utils import shorten_middle - -router = APIRouter() - - -@router.post('/chats/message') -async def _message(request: Request): - body = await request.json() - check_request_body(body, ['message']) - user_message_content = body['message'] - history = body.get('history', []) - - config.logger.info(f'POST /chats/message: len(message):{len(user_message_content)};len(history):{len(history)}') - - # chain = MessageChainService(config).create(user_message_content, history) - - response = MessageMultiStepsChainService(config).load_response(user_message_content, history) - - config.logger.info('Starting event-stream') - - return StreamingResponse( - stream_response(response), media_type="text/event-stream" - ) - - -@router.post("/chats/summarize") -async def _summary(request: Request): - body = await request.json() - history = body.get('history', []) - - config.logger.info(f'POST /chats/summarize: len(history):{len(history)}') - - chain = SummaryChainService(config).create(history) - result = chain.invoke({}) - - config.logger.info(f'Result: {shorten_middle(result, 30)}') - - return json_output(result) diff --git a/tutor-assistant/tutor_assistant/controller/api/demo_controller.py b/tutor-assistant/tutor_assistant/controller/api/demo_controller.py deleted file mode 100644 index 527db8e..0000000 --- a/tutor-assistant/tutor_assistant/controller/api/demo_controller.py +++ /dev/null @@ -1,94 +0,0 @@ -from fastapi import APIRouter -from langchain_community.vectorstores import FAISS -from langchain_core.documents import Document -from langchain_core.messages import ChatMessage -from langchain_core.prompts import ChatPromptTemplate - -from tutor_assistant.controller.config.domain_config import config -from tutor_assistant.domain.chats.message_multi_steps_chain_service import MessageMultiStepsChainService - -router = APIRouter() - - -@router.post('/demo/multi-steps') -async def _get_multi_steps(): - # user_message_content = 'Was mache ich in Artemis bei Exited Prematurely' - # history = [] - # MessageMultiStepsChainService(config).load_response(user_message_content, history) - - # user_message_content = 'Welches Datum haben wir heute?' - # history = [] - # MessageMultiStepsChainService(config).create(user_message_content, history) - # - # user_message_content = 'Wie viele Übungsblätter gibt es?' - # history = [] - # MessageMultiStepsChainService(config).create(user_message_content, history) - # - user_message_content = 'Was mache ich bei Exited Prematurely' - history = [] - response = MessageMultiStepsChainService(config).load_response(user_message_content, history) - - for item in response: - if 'context' in item: - print(item['context']) - if 'answer' in item: - print(item['answer']) - - -@router.get("/demo/messages") -async def _get_demo_messages(): - template = ChatPromptTemplate.from_messages( - [ - ('user', 'Hallo?'), - ('ai', 'Wie kann ich dir helfen?'), - ('user', 'Mir ist langweilig') - ] - ) - - -@router.post('/demo/meta-docs') -async def _meta_docs(): - documents = [ - Document(page_content="Übungsblatt 0, Aufgabe A"), - Document(page_content="Übungsblatt 0, Aufgabe B"), - Document(page_content="Übungsblatt 0, Aufgabe C"), - Document(page_content="Übungsblatt 1, Aufgabe A"), - Document(page_content="Übungsblatt 1, Aufgabe B"), - Document(page_content="Übungsblatt 1, Aufgabe C"), - Document(page_content="Übungsblatt 2, Aufgabe A"), - Document(page_content="Übungsblatt 2, Aufgabe B"), - Document(page_content="Übungsblatt 2, Aufgabe C"), - Document(page_content="Übungsblatt 3, Aufgabe A"), - Document(page_content="Übungsblatt 3, Aufgabe B"), - Document(page_content="Übungsblatt 5, Aufgabe A"), - ] - - queries = [ - 'Übungsblatt 2 Aufgabe A', - - 'Worum geht es in Aufgabe A auf Übungsblatt 2?', - 'Worum geht es in Aufgabe A auf Übungsblatt zwei?', - 'Worum geht es in Aufgabe A auf dem 2. Übungsblatt?', - 'Worum geht es in Aufgabe A auf dem zweiten Übungsblatt?', - - 'Worum geht es in Aufgabe 1 auf Übungsblatt 2?', - 'Worum geht es in Aufgabe 1 auf Übungsblatt zwei?', - 'Worum geht es in Aufgabe 1 auf dem 2. Übungsblatt?', - 'Worum geht es in Aufgabe 1 auf dem zweiten Übungsblatt?', - - 'Worum geht es in Aufgabe eins auf Übungsblatt 2?', - 'Worum geht es in Aufgabe eins auf Übungsblatt zwei?', - 'Worum geht es in Aufgabe eins auf dem 2. Übungsblatt?', - 'Worum geht es in Aufgabe eins auf dem zweiten Übungsblatt?', - - 'Worum geht es in der ersten Aufgabe auf Übungsblatt 2?', - 'Worum geht es in der ersten Aufgabe auf Übungsblatt zwei?', - 'Worum geht es in der ersten Aufgabe auf dem 2. Übungsblatt?', - 'Worum geht es in der ersten Aufgabe auf dem zweiten Übungsblatt?', - ] - - vectorstore = FAISS.from_documents(documents, config.embeddings) - - for query in queries: - result = vectorstore.similarity_search_with_score(query, k=1) - print(result[0][0], result[0][1]) diff --git a/tutor-assistant/tutor_assistant/controller/api/documents_controller.py b/tutor-assistant/tutor_assistant/controller/api/documents_controller.py deleted file mode 100644 index 7d4db09..0000000 --- a/tutor-assistant/tutor_assistant/controller/api/documents_controller.py +++ /dev/null @@ -1,45 +0,0 @@ -from fastapi import Request, APIRouter - -from tutor_assistant.controller.config.domain_config import config -from tutor_assistant.controller.config.loaders_config import loader_creators -from tutor_assistant.controller.utils.api_utils import check_request_body -from tutor_assistant.controller.utils.loaders_utils import get_loader -from tutor_assistant.domain.documents.document_service import DocumentService - -router = APIRouter() - - -@router.post('/documents/add') -async def _add_document(request: Request): - body: dict = await request.json() - check_request_body(body, ['title', 'originalKey', 'loaderType', 'loaderParams', 'isCalendar']) - title: str = body['title'] - original_key: str = body['originalKey'] - loader_type: str = body['loaderType'] - loader_params: dict = body['loaderParams'] - is_calendar: bool = body['isCalendar'] - - config.logger.info( - f'POST /documents/add: title={title}; original_key={original_key}') - - loader = get_loader(loader_creators, title, loader_type, loader_params) - - ids = DocumentService(config).add(loader, title, original_key, is_calendar) - - config.logger.info(f'Result: {ids}') - - return ids - - -@router.post('/documents/delete') -async def _delete_document(request: Request): - body = await request.json() - ids: list[str] = body - - config.logger.info(f'POST /documents/delete: ids:{ids}') - - result = DocumentService(config).delete(ids) - - config.logger.info(f'Result: {result}') - - return True if result is None else result diff --git a/tutor-assistant/tutor_assistant/controller/config/domain_config.py b/tutor-assistant/tutor_assistant/controller/config/domain_config.py index 25a9df9..74d3f11 100644 --- a/tutor-assistant/tutor_assistant/controller/config/domain_config.py +++ b/tutor-assistant/tutor_assistant/controller/config/domain_config.py @@ -7,24 +7,19 @@ from tutor_assistant.domain.domain_config import DomainConfig from tutor_assistant.domain.vector_stores.chroma_repo import ChromaRepo -# _use_base_retriever = True +_retriever_name = os.getenv('RETRIEVER_NAME') +_embed_meta_docs = True if os.getenv('EMBED_META_DOCS') == 'true' else False +_vector_store_suffix = os.getenv('VECTOR_STORE_SUFFIX') _embeddings = OpenAIEmbeddings(model='text-embedding-3-large') -# _store = ChromaRepo(f"{os.getenv('DATA_DIR')}/chroma_ws2324_with_meta_docs_index", _embeddings) - -# if _use_base_retriever: -# _store = ChromaRepo(f"{os.getenv('DATA_DIR')}/chroma_ws2324_no_meta_docs_index", _embeddings) - -_store = ChromaRepo(f"{os.getenv('DATA_DIR')}/chroma_index", _embeddings) - config = DomainConfig( ChatOpenAI(model='gpt-4o', temperature=0), - # get_remote_ollama_chat_model('llama3.1:8b'), _embeddings, - _store, + ChromaRepo(f"{os.getenv('DATA_DIR')}/chroma_store_{_vector_store_suffix}", _embeddings), load_resources(f'{os.getcwd()}/resources'), get_logger(), "Deutsch", - # _use_base_retriever + _retriever_name, + _embed_meta_docs ) diff --git a/tutor-assistant/tutor_assistant/domain/chats/message_chain_service.py b/tutor-assistant/tutor_assistant/domain/chats/message_chain_service.py index 656f479..cef2f6c 100644 --- a/tutor-assistant/tutor_assistant/domain/chats/message_chain_service.py +++ b/tutor-assistant/tutor_assistant/domain/chats/message_chain_service.py @@ -1,14 +1,18 @@ -from typing import Any +from typing import Any, Callable from langchain.retrievers import SelfQueryRetriever from langchain_core.documents import Document from langchain_core.output_parsers import StrOutputParser from langchain_core.prompts import ChatPromptTemplate -from langchain_core.runnables import RunnablePassthrough, RunnableSerializable +from langchain_core.runnables import RunnablePassthrough, RunnableSerializable, chain from tutor_assistant.controller.utils.data_transfer_utils import messages_from_history from tutor_assistant.controller.utils.langchain_utils import escape_prompt -from tutor_assistant.domain.documents.retrievers.queries_loader_retriever import QueriesLoaderRetriever +from tutor_assistant.domain.chats.message_multi_steps_response_loader import MessageMultiStepsResponseLoader +from tutor_assistant.domain.documents.retrievers.combined_retriever import CombinedRetriever +from tutor_assistant.domain.documents.retrievers.generated_queries_retriever import GeneratedQueriesRetriever +from tutor_assistant.domain.documents.retrievers.with_references_retriever import WithReferencesRetriever +from tutor_assistant.domain.documents.utils.vector_store_utils import similarity_search_with_score from tutor_assistant.domain.domain_config import DomainConfig from tutor_assistant.domain.utils.templates import prepend_base_template @@ -17,27 +21,32 @@ class MessageChainService: def __init__(self, config: DomainConfig): self._config = config - def create(self, user_message_content: str, history: list[dict[str, str]]) -> RunnableSerializable: - messages = self._get_all_messages(user_message_content, history) + def load_response(self, user_message_content: str, history: list[dict[str, str]]): + retriever_name = self._config.retriever_name - chat_prompt = self._get_chat_prompt(messages) - - model_chain = self._get_model_chain(chat_prompt) - retriever = QueriesLoaderRetriever(self._config) + if retriever_name == 'multi-steps-retriever': + yield from MessageMultiStepsResponseLoader(self._config).load_response(user_message_content, history) + return + messages = self._get_all_messages(user_message_content, history) - retriever_chain = (lambda _: messages) | retriever + retrievers: dict[str, Callable[[str], RunnableSerializable[Any, list[Document]]]] = { + 'base-retriever': self._get_base_retriever_chain, + 'self-query-retriever': self._get_self_query_retriever_chain, + 'queries-from-chat-model-retriever': lambda _: self._get_generated_queries_retriever(messages), + 'combined-retriever': lambda _: self._get_combined_retriever(messages), + 'with-references-retriever': self._get_with_references_retriever, + } - if self._config.use_base_retriever: - retriever_chain = (lambda _: self._search_with_score(user_message_content)) - # retriever_chain = (lambda _: user_message_content) | self._get_self_query_retriever_chain(user_message_content) + if retriever_name not in retrievers: + raise ValueError(f'Retriever {retriever_name} not found') + chat_prompt = self._get_chat_prompt(messages) + model_chain = self._get_model_chain(chat_prompt) + retriever = retrievers[retriever_name] + retriever_chain = retriever(user_message_content) - return ( - RunnablePassthrough - .assign(context=retriever_chain) - .assign(answer=model_chain) - ) + yield from (RunnablePassthrough.assign(context=retriever_chain).assign(answer=model_chain)).stream({}) @staticmethod def _get_all_messages(user_message_content: str, history) -> list[tuple[str, str]]: @@ -66,42 +75,43 @@ def _get_model_chain(self, prompt) -> RunnableSerializable[Any, str]: return prompt | model | parser def _get_base_retriever_chain(self, query: str) -> RunnableSerializable[Any, list[Document]]: - retriever = self._config.vector_store_manager.load().as_retriever() - return (lambda _: query) | retriever + self._config.logger.info(f'BaseRetriever for "{query}"') + + @chain + def search_chain(q: str): + return similarity_search_with_score(self._config.vector_store_manager.load(), q) + + return (lambda _: query) | search_chain def _get_self_query_retriever_chain(self, query: str) -> RunnableSerializable[Any, list[Document]]: - metadata_field_info = [] + self._config.logger.info(f'SelfQueryRetriever for "{query}"') + document_content_description = "Informationen zu Programmieren an der Uni" llm = self._config.chat_model retriever = SelfQueryRetriever.from_llm( - llm, self._config.vector_store_manager.load(), document_content_description, metadata_field_info, + llm, self._config.vector_store_manager.load(), document_content_description, [], enable_limit=True, verbose=True ) - # query += " !Gib mir maximal 20 Dokumente!" - print(retriever.invoke(query)) - + query += " Gib mir maximal 5 Dokumente!" return (lambda _: query) | retriever - def _search_with_score(self, query: str) -> list[Document]: - self._config.logger.info(f'Base Retriever: Running for "{query}') - try: - docs, scores = zip( - *self._config.vector_store_manager.load().similarity_search_with_score( - query, - k=4 - ) - ) - except Exception as e: - print('Exception:', e) - return [] - result = [] - doc: Document - for doc, np_score in zip(docs, scores): - score = float(np_score) - doc.metadata['score'] = score - if np_score < 2: - result.append(doc) - - return result + def _get_combined_retriever(self, messages: list[tuple[str, str]]) -> RunnableSerializable[ + Any, list[Document]]: + + self._config.logger.info(f'CombinedRetriever for "{messages}"') + + return (lambda _: messages) | CombinedRetriever(self._config) + + def _get_generated_queries_retriever(self, messages: list[tuple[str, str]]) -> RunnableSerializable[ + Any, list[Document]]: + + self._config.logger.info(f'QueriesFromChatModelRetriever for "{messages}"') + + return (lambda _: messages) | GeneratedQueriesRetriever(self._config) + + def _get_with_references_retriever(self, query: str) -> RunnableSerializable[Any, list[Document]]: + self._config.logger.info(f'WithReferencesRetriever for "{query}"') + + return (lambda _: [query]) | WithReferencesRetriever(self._config) diff --git a/tutor-assistant/tutor_assistant/domain/chats/message_multi_steps_chain_service.py b/tutor-assistant/tutor_assistant/domain/chats/message_multi_steps_chain_service.py deleted file mode 100644 index 4ac5ceb..0000000 --- a/tutor-assistant/tutor_assistant/domain/chats/message_multi_steps_chain_service.py +++ /dev/null @@ -1,101 +0,0 @@ -from typing import Iterator, Generator, Any - -from langchain_core.documents import Document -from langchain_core.output_parsers import StrOutputParser -from langchain_core.prompts import ChatPromptTemplate -from langchain_core.runnables import RunnablePassthrough, RunnableSerializable - -from tutor_assistant.controller.utils.data_transfer_utils import messages_from_history -from tutor_assistant.controller.utils.langchain_utils import escape_prompt -from tutor_assistant.domain.documents.retrievers.with_references_retriever import WithReferencesRetriever -from tutor_assistant.domain.domain_config import DomainConfig -from tutor_assistant.domain.utils.templates import prepend_base_template - - -class MessageMultiStepsChainService: - def __init__(self, config: DomainConfig): - self._config = config - - def load_response(self, user_message_content: str, history: list[dict[str, str]]) -> Generator[Any, Any, None]: - messages = self._get_all_messages(user_message_content, history) - yield from self._get_response_for_queries(messages, [user_message_content], ['first.txt', 'last.txt']) - - def _get_response_for_queries( - self, messages: list[tuple[str, str]], queries: list[str], templates: list[str] - ) -> Generator[Any, Any, None]: - - retriever_chain = self._get_retriever_chain(queries) - model_chain = self._get_model_chain(messages, templates[0]) - - result = RunnablePassthrough.assign(context=retriever_chain).assign(answer=model_chain).stream({}) - - contexts = [] - answer_start = '' - - for item in result: - if 'context' in item: - contexts.append(item) - elif 'answer' in item: - answer_start += item['answer'] - - if '!!!QUERIES!!!' in answer_start: - print('!!!QUERIES!!!') - queries = self._get_queries_from_answer(result) - yield from self._get_response_for_queries(messages, queries, templates[1:]) - break - elif '!!!RESPONSE!!!' in answer_start: - print('!!!RESPONSE!!!') - yield from contexts - yield from self._yield_response(result) - break - elif len(answer_start) > 14: - print('Long enough') - yield from contexts - yield answer_start - yield from self._yield_response(result) - break - - @staticmethod - def _get_all_messages(user_message_content: str, history) -> list[tuple[str, str]]: - messages = [] - for msg in messages_from_history(history): - messages.append(msg) - messages.append(('user', escape_prompt(user_message_content))) - - return messages - - def _get_queries_from_answer(self, result: Iterator) -> list[str]: - answer = '' - for item in result: - if 'answer' in item: - answer += item['answer'] - - queries = answer.split(';') - self._config.logger.info(f'Queries from chat model: {queries}') - return queries - - @staticmethod - def _yield_response(result: Iterator) -> Generator[Any, Any, None]: - for item in result: - yield item - - def _get_retriever_chain(self, queries: list[str]) -> RunnableSerializable[Any, list[Document]]: - return (lambda _: queries) | WithReferencesRetriever(self._config) - - def _get_model_chain(self, messages: list[tuple[str, str]], template: str): - prompt = self._get_chat_prompt(messages, template) - model = self._config.chat_model - parser = StrOutputParser() - - return prompt | model | parser - - def _get_chat_prompt(self, messages: list[tuple[str, str]], template: str) -> ChatPromptTemplate: - template = self._config.resources['prompt_templates']['multi_steps'][template] - complete_template = prepend_base_template(self._config, template) - - prompt_messages = [('system', complete_template)] - prompt_messages.extend(messages) - - prompt_template = ChatPromptTemplate.from_messages(prompt_messages) - - return prompt_template diff --git a/tutor-assistant/tutor_assistant/domain/documents/document_service.py b/tutor-assistant/tutor_assistant/domain/documents/document_service.py index 1c25d53..77f8e9a 100644 --- a/tutor-assistant/tutor_assistant/domain/documents/document_service.py +++ b/tutor-assistant/tutor_assistant/domain/documents/document_service.py @@ -24,8 +24,6 @@ def add(self, loader: BaseLoader, title: str, original_key: str, is_calendar: bo doc.metadata['isCalendar'] = is_calendar ids.append(doc.id) - meta_docs = self._get_meta_docs(documents) - store = self._config.vector_store_manager.load() store_ids = store.add_documents(documents) @@ -33,9 +31,11 @@ def add(self, loader: BaseLoader, title: str, original_key: str, is_calendar: bo raise RuntimeError( f'ids and store_ids should be equal, but got ids={ids} and store_ids={store_ids}') - if len(meta_docs) > 0: - meta_doc_ids = store.add_documents(meta_docs) - store_ids.extend(meta_doc_ids) + if self._config.embed_meta_docs: + meta_docs = self._get_meta_docs(documents) + if len(meta_docs) > 0: + meta_doc_ids = store.add_documents(meta_docs) + store_ids.extend(meta_doc_ids) self._config.vector_store_manager.save(store) diff --git a/tutor-assistant/tutor_assistant/domain/documents/retrievers/queries_loader_retriever.py b/tutor-assistant/tutor_assistant/domain/documents/retrievers/queries_loader_retriever.py deleted file mode 100644 index 377d32a..0000000 --- a/tutor-assistant/tutor_assistant/domain/documents/retrievers/queries_loader_retriever.py +++ /dev/null @@ -1,38 +0,0 @@ -from typing import Any - -from langchain_core.callbacks import CallbackManagerForRetrieverRun -from langchain_core.documents import Document -from langchain_core.prompts import ChatPromptTemplate -from langchain_core.retrievers import BaseRetriever - -from tutor_assistant.domain.documents.retrievers.with_references_retriever import WithReferencesRetriever -from tutor_assistant.domain.domain_config import DomainConfig - - -class QueriesLoaderRetriever(BaseRetriever): - def __init__(self, config: DomainConfig, *args: Any, **kwargs: Any): - super().__init__(*args, **kwargs) - self._chat_model = config.chat_model - self._vector_store = config.vector_store_manager.load() - self._config = config - - def _get_relevant_documents( - self, messages: list[tuple[str, str]], *, run_manager: CallbackManagerForRetrieverRun - ) -> list[Document]: - pass - # queries = self._get_queries(messages) - # return WithReferencesRetriever(self._config).search(queries) - - def _get_queries(self, messages: list[tuple[str, str]]) -> list[str]: - chain = self._get_chat_prompt(messages) | self._chat_model - content = chain.invoke({}).content - print('content', content) - queries = content.split(';') - - return queries - - def _get_chat_prompt(self, messages: list[tuple[str, str]]) -> ChatPromptTemplate: - multiple_prompts = self._config.resources['prompt_templates']['hybrid_retriever'][ - 'multiple_retriever_queries_3.txt'] - prompt_messages = messages + [('system', multiple_prompts)] - return ChatPromptTemplate.from_messages(prompt_messages) diff --git a/tutor-assistant/tutor_assistant/domain/documents/retrievers/with_references_retriever.py b/tutor-assistant/tutor_assistant/domain/documents/retrievers/with_references_retriever.py index 54f009a..e12dc3b 100644 --- a/tutor-assistant/tutor_assistant/domain/documents/retrievers/with_references_retriever.py +++ b/tutor-assistant/tutor_assistant/domain/documents/retrievers/with_references_retriever.py @@ -43,7 +43,7 @@ def _search_with_score(self, query: str) -> list[Document]: docs, scores = zip( *self._vector_store.similarity_search_with_score( query, - k=8 + k=5 ) ) except Exception as e: @@ -54,7 +54,7 @@ def _search_with_score(self, query: str) -> list[Document]: for doc, np_score in zip(docs, scores): score = float(np_score) doc.metadata['score'] = score - if np_score < 1.1: + if np_score < 5: result.append(doc) return result diff --git a/tutor-assistant/tutor_assistant/domain/domain_config.py b/tutor-assistant/tutor_assistant/domain/domain_config.py index 46c446c..e850e82 100644 --- a/tutor-assistant/tutor_assistant/domain/domain_config.py +++ b/tutor-assistant/tutor_assistant/domain/domain_config.py @@ -15,7 +15,8 @@ def __init__(self, resources: dict[str, Any], logger: logging.Logger, language: str, - use_base_retriever: bool = False, + retriever_name: str, + embed_meta_docs: bool ): self.chat_model = chat_model self.embeddings = embeddings @@ -23,7 +24,8 @@ def __init__(self, self.resources = resources self.logger = logger self.language = language - self.use_base_retriever = use_base_retriever + self.retriever_name = retriever_name + self.embed_meta_docs = embed_meta_docs logger.info('Application configuration complete.') - logger.info(f"Using {'base' if use_base_retriever else 'hybrid'} retriever") + logger.info(f"retriever_name: {retriever_name}, embed_meta_docs: {embed_meta_docs}") diff --git a/tutor-assistant/tutor_assistant/utils/list_utils.py b/tutor-assistant/tutor_assistant/utils/list_utils.py index 981bece..6b60cb9 100644 --- a/tutor-assistant/tutor_assistant/utils/list_utils.py +++ b/tutor-assistant/tutor_assistant/utils/list_utils.py @@ -1,9 +1,9 @@ -from typing import List, Callable, TypeVar +from typing import Callable, TypeVar T = TypeVar("T") -def distinct_by(get_distinct_key: Callable[[T], object], items: List[T]) -> List[T]: +def distinct_by(get_distinct_key: Callable[[T], object], items: list[T]) -> list[T]: seen = set() result = [] for item in items: