diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/ChatCharacter.kt b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/ChatCharacter.kt new file mode 100644 index 0000000..70a253c --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/ChatCharacter.kt @@ -0,0 +1,115 @@ +package kr.co.vividnext.sodalive.chat.character + +import kr.co.vividnext.sodalive.common.BaseEntity +import javax.persistence.CascadeType +import javax.persistence.Entity +import javax.persistence.FetchType +import javax.persistence.OneToMany + +@Entity +class ChatCharacter( + val characterUUID: String, + + // 캐릭터 이름 (API 키 내에서 유일해야 함) + val name: String, + + // 캐릭터 설명 + val description: String, + + // AI 시스템 프롬프트 + val systemPrompt: String, + + // 나이 + val age: Int? = null, + + // 성별 + val gender: String? = null, + + // mbti + val mbti: String? = null, + + // 말투 패턴 설명 + val speechPattern: String? = null, + + // 대화 스타일 + val speechStyle: String? = null, + + // 외모 설명 + val appearance: String? = null, + + val isActive: Boolean = true +) : BaseEntity() { + var imagePath: String? = null + + @OneToMany(mappedBy = "chatCharacter", cascade = [CascadeType.ALL], fetch = FetchType.LAZY) + var memories: MutableList = mutableListOf() + + @OneToMany(mappedBy = "chatCharacter", cascade = [CascadeType.ALL], fetch = FetchType.LAZY) + var personalities: MutableList = mutableListOf() + + @OneToMany(mappedBy = "chatCharacter", cascade = [CascadeType.ALL], fetch = FetchType.LAZY) + var backgrounds: MutableList = mutableListOf() + + @OneToMany(mappedBy = "chatCharacter", cascade = [CascadeType.ALL], fetch = FetchType.LAZY) + var relationships: MutableList = mutableListOf() + + @OneToMany(mappedBy = "chatCharacter", cascade = [CascadeType.ALL], fetch = FetchType.LAZY) + var tagMappings: MutableList = mutableListOf() + + @OneToMany(mappedBy = "chatCharacter", cascade = [CascadeType.ALL], fetch = FetchType.LAZY) + var valueMappings: MutableList = mutableListOf() + + @OneToMany(mappedBy = "chatCharacter", cascade = [CascadeType.ALL], fetch = FetchType.LAZY) + var hobbyMappings: MutableList = mutableListOf() + + @OneToMany(mappedBy = "chatCharacter", cascade = [CascadeType.ALL], fetch = FetchType.LAZY) + var goalMappings: MutableList = mutableListOf() + + // 태그 추가 헬퍼 메소드 + fun addTag(tag: ChatCharacterTag) { + val mapping = ChatCharacterTagMapping(this, tag) + tagMappings.add(mapping) + } + + // 가치관 추가 헬퍼 메소드 + fun addValue(value: ChatCharacterValue) { + val mapping = ChatCharacterValueMapping(this, value) + valueMappings.add(mapping) + } + + // 취미 추가 헬퍼 메소드 + fun addHobby(hobby: ChatCharacterHobby) { + val mapping = ChatCharacterHobbyMapping(this, hobby) + hobbyMappings.add(mapping) + } + + // 목표 추가 헬퍼 메소드 + fun addGoal(goal: ChatCharacterGoal) { + val mapping = ChatCharacterGoalMapping(this, goal) + goalMappings.add(mapping) + } + + // 기억 추가 헬퍼 메소드 + fun addMemory(title: String, content: String, emotion: String) { + val memory = ChatCharacterMemory(title, content, emotion, this) + memories.add(memory) + } + + // 성격 추가 헬퍼 메소드 + fun addPersonality(trait: String, description: String) { + val personality = ChatCharacterPersonality(trait, description, this) + personalities.add(personality) + } + + // 배경 추가 헬퍼 메소드 + fun addBackground(topic: String, description: String) { + val background = ChatCharacterBackground(topic, description, this) + backgrounds.add(background) + } + + // 관계 추가 헬퍼 메소드 + fun addRelationship(relationShip: String) { + val relationship = ChatCharacterRelationship(relationShip, this) + relationships.add(relationship) + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/ChatCharacterBackground.kt b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/ChatCharacterBackground.kt new file mode 100644 index 0000000..4c8b4d6 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/ChatCharacterBackground.kt @@ -0,0 +1,24 @@ +package kr.co.vividnext.sodalive.chat.character + +import kr.co.vividnext.sodalive.common.BaseEntity +import javax.persistence.Entity +import javax.persistence.FetchType +import javax.persistence.JoinColumn +import javax.persistence.ManyToOne + +/** + * 캐릭터 배경 정보 + */ + +@Entity +class ChatCharacterBackground( + // 배경 주제 + val topic: String, + + // 배경 설명 + val description: String, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "chat_character_id") + val chatCharacter: ChatCharacter +) : BaseEntity() diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/ChatCharacterGoal.kt b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/ChatCharacterGoal.kt new file mode 100644 index 0000000..4f6d198 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/ChatCharacterGoal.kt @@ -0,0 +1,22 @@ +package kr.co.vividnext.sodalive.chat.character + +import kr.co.vividnext.sodalive.common.BaseEntity +import javax.persistence.Column +import javax.persistence.Entity +import javax.persistence.OneToMany +import javax.persistence.Table +import javax.persistence.UniqueConstraint + +/** + * 캐릭터 목표 + */ + +@Entity +@Table(uniqueConstraints = [UniqueConstraint(columnNames = ["goal"])]) +class ChatCharacterGoal( + @Column(nullable = false) + val goal: String +) : BaseEntity() { + @OneToMany(mappedBy = "goal") + var goalMappings: MutableList = mutableListOf() +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/ChatCharacterGoalMapping.kt b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/ChatCharacterGoalMapping.kt new file mode 100644 index 0000000..0d6b070 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/ChatCharacterGoalMapping.kt @@ -0,0 +1,22 @@ +package kr.co.vividnext.sodalive.chat.character + +import kr.co.vividnext.sodalive.common.BaseEntity +import javax.persistence.Entity +import javax.persistence.FetchType +import javax.persistence.JoinColumn +import javax.persistence.ManyToOne + +/** + * ChatCharacter와 ChatCharacterGoal 간의 매핑 엔티티 + * ChatCharacterGoal의 중복을 방지하기 위한 매핑 테이블 + */ +@Entity +class ChatCharacterGoalMapping( + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "chat_character_id") + val chatCharacter: ChatCharacter, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "goal_id") + val goal: ChatCharacterGoal +) : BaseEntity() diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/ChatCharacterHobby.kt b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/ChatCharacterHobby.kt new file mode 100644 index 0000000..df894ff --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/ChatCharacterHobby.kt @@ -0,0 +1,22 @@ +package kr.co.vividnext.sodalive.chat.character + +import kr.co.vividnext.sodalive.common.BaseEntity +import javax.persistence.Column +import javax.persistence.Entity +import javax.persistence.OneToMany +import javax.persistence.Table +import javax.persistence.UniqueConstraint + +/** + * 캐릭터 취미 + */ + +@Entity +@Table(uniqueConstraints = [UniqueConstraint(columnNames = ["hobby"])]) +class ChatCharacterHobby( + @Column(nullable = false) + val hobby: String +) : BaseEntity() { + @OneToMany(mappedBy = "hobby") + var hobbyMappings: MutableList = mutableListOf() +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/ChatCharacterHobbyMapping.kt b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/ChatCharacterHobbyMapping.kt new file mode 100644 index 0000000..1f5a1b9 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/ChatCharacterHobbyMapping.kt @@ -0,0 +1,22 @@ +package kr.co.vividnext.sodalive.chat.character + +import kr.co.vividnext.sodalive.common.BaseEntity +import javax.persistence.Entity +import javax.persistence.FetchType +import javax.persistence.JoinColumn +import javax.persistence.ManyToOne + +/** + * ChatCharacter와 ChatCharacterHobby 간의 매핑 엔티티 + * ChatCharacterHobby의 중복을 방지하기 위한 매핑 테이블 + */ +@Entity +class ChatCharacterHobbyMapping( + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "chat_character_id") + val chatCharacter: ChatCharacter, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "hobby_id") + val hobby: ChatCharacterHobby +) : BaseEntity() diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/ChatCharacterMemory.kt b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/ChatCharacterMemory.kt new file mode 100644 index 0000000..b39cbd4 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/ChatCharacterMemory.kt @@ -0,0 +1,27 @@ +package kr.co.vividnext.sodalive.chat.character + +import kr.co.vividnext.sodalive.common.BaseEntity +import javax.persistence.Entity +import javax.persistence.FetchType +import javax.persistence.JoinColumn +import javax.persistence.ManyToOne + +/** + * 캐릭터 기억 + */ + +@Entity +class ChatCharacterMemory( + // 기억 제목 + val title: String, + + // 기억 내용 + val content: String, + + // 감정 + val emotion: String, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "chat_character_id") + val chatCharacter: ChatCharacter +) : BaseEntity() diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/ChatCharacterPersonality.kt b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/ChatCharacterPersonality.kt new file mode 100644 index 0000000..d6bd7b3 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/ChatCharacterPersonality.kt @@ -0,0 +1,24 @@ +package kr.co.vividnext.sodalive.chat.character + +import kr.co.vividnext.sodalive.common.BaseEntity +import javax.persistence.Entity +import javax.persistence.FetchType +import javax.persistence.JoinColumn +import javax.persistence.ManyToOne + +/** + * 캐릭터 성격 특성 + */ + +@Entity +class ChatCharacterPersonality( + // 성격 특성 + val trait: String, + + // 성격 특성 설명 + val description: String, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "chat_character_id") + val chatCharacter: ChatCharacter +) : BaseEntity() diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/ChatCharacterRelationship.kt b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/ChatCharacterRelationship.kt new file mode 100644 index 0000000..ba3932c --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/ChatCharacterRelationship.kt @@ -0,0 +1,20 @@ +package kr.co.vividnext.sodalive.chat.character + +import kr.co.vividnext.sodalive.common.BaseEntity +import javax.persistence.Entity +import javax.persistence.FetchType +import javax.persistence.JoinColumn +import javax.persistence.ManyToOne + +/** + * 캐릭터 관계 + */ + +@Entity +class ChatCharacterRelationship( + val relationShip: String, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "chat_character_id") + val chatCharacter: ChatCharacter +) : BaseEntity() diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/ChatCharacterTag.kt b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/ChatCharacterTag.kt new file mode 100644 index 0000000..d00f09f --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/ChatCharacterTag.kt @@ -0,0 +1,22 @@ +package kr.co.vividnext.sodalive.chat.character + +import kr.co.vividnext.sodalive.common.BaseEntity +import javax.persistence.Column +import javax.persistence.Entity +import javax.persistence.OneToMany +import javax.persistence.Table +import javax.persistence.UniqueConstraint + +/** + * 캐릭터 태그 + */ + +@Entity +@Table(uniqueConstraints = [UniqueConstraint(columnNames = ["tag"])]) +class ChatCharacterTag( + @Column(nullable = false) + val tag: String +) : BaseEntity() { + @OneToMany(mappedBy = "tag") + var tagMappings: MutableList = mutableListOf() +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/ChatCharacterTagMapping.kt b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/ChatCharacterTagMapping.kt new file mode 100644 index 0000000..95d09fc --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/ChatCharacterTagMapping.kt @@ -0,0 +1,22 @@ +package kr.co.vividnext.sodalive.chat.character + +import kr.co.vividnext.sodalive.common.BaseEntity +import javax.persistence.Entity +import javax.persistence.FetchType +import javax.persistence.JoinColumn +import javax.persistence.ManyToOne + +/** + * ChatCharacter와 ChatCharacterTag 간의 매핑 엔티티 + * ChatCharacterTag의 중복을 방지하기 위한 매핑 테이블 + */ +@Entity +class ChatCharacterTagMapping( + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "chat_character_id") + val chatCharacter: ChatCharacter, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "tag_id") + val tag: ChatCharacterTag +) : BaseEntity() diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/ChatCharacterValue.kt b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/ChatCharacterValue.kt new file mode 100644 index 0000000..70fa863 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/ChatCharacterValue.kt @@ -0,0 +1,22 @@ +package kr.co.vividnext.sodalive.chat.character + +import kr.co.vividnext.sodalive.common.BaseEntity +import javax.persistence.Column +import javax.persistence.Entity +import javax.persistence.OneToMany +import javax.persistence.Table +import javax.persistence.UniqueConstraint + +/** + * 캐릭터 가치관 + */ + +@Entity +@Table(uniqueConstraints = [UniqueConstraint(columnNames = ["value"])]) +class ChatCharacterValue( + @Column(nullable = false) + val value: String +) : BaseEntity() { + @OneToMany(mappedBy = "value") + var valueMappings: MutableList = mutableListOf() +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/ChatCharacterValueMapping.kt b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/ChatCharacterValueMapping.kt new file mode 100644 index 0000000..9a730b0 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/ChatCharacterValueMapping.kt @@ -0,0 +1,22 @@ +package kr.co.vividnext.sodalive.chat.character + +import kr.co.vividnext.sodalive.common.BaseEntity +import javax.persistence.Entity +import javax.persistence.FetchType +import javax.persistence.JoinColumn +import javax.persistence.ManyToOne + +/** + * ChatCharacter와 ChatCharacterValue 간의 매핑 엔티티 + * ChatCharacterValue의 중복을 방지하기 위한 매핑 테이블 + */ +@Entity +class ChatCharacterValueMapping( + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "chat_character_id") + val chatCharacter: ChatCharacter, + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "value_id") + val value: ChatCharacterValue +) : BaseEntity() diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/README.md b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/README.md new file mode 100644 index 0000000..14b1671 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/README.md @@ -0,0 +1,118 @@ +# ChatCharacter 엔티티 관계 구현 + +## 개요 + +이 구현은 ChatCharacter와 다른 엔티티들 간의 1:N 관계를 설정합니다. ChatCharacter가 저장될 때 관련 엔티티들도 함께 저장되며, Tag, Value, Hobby, Goal은 중복을 허용하지 않고 기존 내용과 동일한 내용이 들어오면 관계만 맺습니다. + +## 엔티티 구조 + +### 주요 엔티티 + +- **ChatCharacter**: 메인 엔티티로, 캐릭터의 기본 정보를 저장합니다. +- **ChatCharacterMemory**: 캐릭터의 기억 정보를 저장합니다. +- **ChatCharacterPersonality**: 캐릭터의 성격 특성을 저장합니다. +- **ChatCharacterBackground**: 캐릭터의 배경 정보를 저장합니다. +- **ChatCharacterRelationship**: 캐릭터의 관계 정보를 저장합니다. + +### 중복 방지를 위한 엔티티 + +- **ChatCharacterTag**: 캐릭터 태그 정보를 저장합니다. 태그 이름은 유일합니다. +- **ChatCharacterValue**: 캐릭터 가치관 정보를 저장합니다. 가치관 이름은 유일합니다. +- **ChatCharacterHobby**: 캐릭터 취미 정보를 저장합니다. 취미 이름은 유일합니다. +- **ChatCharacterGoal**: 캐릭터 목표 정보를 저장합니다. 목표 이름은 유일합니다. + +### 매핑 엔티티 + +- **ChatCharacterTagMapping**: ChatCharacter와 ChatCharacterTag 간의 관계를 맺습니다. +- **ChatCharacterValueMapping**: ChatCharacter와 ChatCharacterValue 간의 관계를 맺습니다. +- **ChatCharacterHobbyMapping**: ChatCharacter와 ChatCharacterHobby 간의 관계를 맺습니다. +- **ChatCharacterGoalMapping**: ChatCharacter와 ChatCharacterGoal 간의 관계를 맺습니다. + +## 관계 설정 + +- ChatCharacter와 Memory, Personality, Background, Relationship은 1:N 관계입니다. +- ChatCharacter와 Tag, Value, Hobby, Goal은 매핑 엔티티를 통한 N:M 관계입니다. +- 모든 관계는 ChatCharacter가 저장될 때 함께 저장됩니다(CascadeType.ALL). + +## 서비스 기능 + +ChatCharacterService는 다음과 같은 기능을 제공합니다: + +1. 캐릭터 생성 및 저장 +2. 캐릭터 조회 (UUID, 이름, 전체 목록) +3. 캐릭터에 태그, 가치관, 취미, 목표 추가 +4. 캐릭터에 기억, 성격 특성, 배경 정보, 관계 추가 +5. 모든 정보를 포함한 캐릭터 생성 + +## 사용 예시 + +```kotlin +// 캐릭터 생성 +val chatCharacter = chatCharacterService.createChatCharacter( + characterUUID = "uuid-123", + name = "AI 어시스턴트", + description = "친절한 AI 어시스턴트", + systemPrompt = "당신은 친절한 AI 어시스턴트입니다." +) + +// 태그 추가 +chatCharacterService.addTagToCharacter(chatCharacter, "친절함") +chatCharacterService.addTagToCharacter(chatCharacter, "도움") + +// 가치관 추가 +chatCharacterService.addValueToCharacter(chatCharacter, "정직") + +// 취미 추가 +chatCharacterService.addHobbyToCharacter(chatCharacter, "독서") + +// 목표 추가 +chatCharacterService.addGoalToCharacter(chatCharacter, "사용자 만족") + +// 기억 추가 +chatCharacterService.addMemoryToChatCharacter( + chatCharacter, + "첫 만남", + "사용자와의 첫 대화", + "기쁨" +) + +// 성격 특성 추가 +chatCharacterService.addPersonalityToChatCharacter( + chatCharacter, + "친절함", + "항상 친절하게 대응합니다." +) + +// 배경 정보 추가 +chatCharacterService.addBackgroundToChatCharacter( + chatCharacter, + "생성 배경", + "사용자를 돕기 위해 만들어졌습니다." +) + +// 관계 추가 +chatCharacterService.addRelationshipToChatCharacter( + chatCharacter, + "사용자와의 관계: 도우미" +) + +// 모든 정보를 포함한 캐릭터 생성 +val completeCharacter = chatCharacterService.createChatCharacterWithDetails( + characterUUID = "uuid-456", + name = "종합 AI", + description = "모든 정보를 가진 AI", + systemPrompt = "당신은 모든 정보를 가진 AI입니다.", + tags = listOf("종합", "지식"), + values = listOf("정확성", "유용성"), + hobbies = listOf("학습", "정보 수집"), + goals = listOf("정확한 정보 제공"), + memories = listOf(Triple("학습 시작", "처음 학습을 시작했습니다.", "호기심")), + personalities = listOf(Pair("분석적", "정보를 분석적으로 처리합니다.")), + backgrounds = listOf(Pair("개발 목적", "정보 제공을 위해 개발되었습니다.")), + relationships = listOf("사용자와의 관계: 정보 제공자") +) +``` + +## 중복 방지 메커니즘 + +Tag, Value, Hobby, Goal 엔티티는 각각 고유한 필드(tag, value, hobby, goal)에 대해 유니크 제약 조건을 가지고 있습니다. 서비스 레이어에서는 이미 존재하는 엔티티를 찾아 재사용하거나, 존재하지 않는 경우 새로 생성합니다. 이를 통해 중복을 방지하고 관계만 맺을 수 있습니다. diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/repository/ChatCharacterGoalRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/repository/ChatCharacterGoalRepository.kt new file mode 100644 index 0000000..621ddc1 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/repository/ChatCharacterGoalRepository.kt @@ -0,0 +1,10 @@ +package kr.co.vividnext.sodalive.chat.character.repository + +import kr.co.vividnext.sodalive.chat.character.ChatCharacterGoal +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +@Repository +interface ChatCharacterGoalRepository : JpaRepository { + fun findByGoal(goal: String): ChatCharacterGoal? +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/repository/ChatCharacterHobbyRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/repository/ChatCharacterHobbyRepository.kt new file mode 100644 index 0000000..e572153 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/repository/ChatCharacterHobbyRepository.kt @@ -0,0 +1,10 @@ +package kr.co.vividnext.sodalive.chat.character.repository + +import kr.co.vividnext.sodalive.chat.character.ChatCharacterHobby +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +@Repository +interface ChatCharacterHobbyRepository : JpaRepository { + fun findByHobby(hobby: String): ChatCharacterHobby? +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/repository/ChatCharacterRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/repository/ChatCharacterRepository.kt new file mode 100644 index 0000000..f9547dd --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/repository/ChatCharacterRepository.kt @@ -0,0 +1,11 @@ +package kr.co.vividnext.sodalive.chat.character.repository + +import kr.co.vividnext.sodalive.chat.character.ChatCharacter +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +@Repository +interface ChatCharacterRepository : JpaRepository { + fun findByCharacterUUID(characterUUID: String): ChatCharacter? + fun findByName(name: String): ChatCharacter? +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/repository/ChatCharacterTagRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/repository/ChatCharacterTagRepository.kt new file mode 100644 index 0000000..fe53163 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/repository/ChatCharacterTagRepository.kt @@ -0,0 +1,10 @@ +package kr.co.vividnext.sodalive.chat.character.repository + +import kr.co.vividnext.sodalive.chat.character.ChatCharacterTag +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +@Repository +interface ChatCharacterTagRepository : JpaRepository { + fun findByTag(tag: String): ChatCharacterTag? +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/repository/ChatCharacterValueRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/repository/ChatCharacterValueRepository.kt new file mode 100644 index 0000000..9c0aa12 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/repository/ChatCharacterValueRepository.kt @@ -0,0 +1,10 @@ +package kr.co.vividnext.sodalive.chat.character.repository + +import kr.co.vividnext.sodalive.chat.character.ChatCharacterValue +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +@Repository +interface ChatCharacterValueRepository : JpaRepository { + fun findByValue(value: String): ChatCharacterValue? +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/service/ChatCharacterService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/service/ChatCharacterService.kt new file mode 100644 index 0000000..3ffad40 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/service/ChatCharacterService.kt @@ -0,0 +1,263 @@ +package kr.co.vividnext.sodalive.chat.character.service + +import kr.co.vividnext.sodalive.chat.character.ChatCharacter +import kr.co.vividnext.sodalive.chat.character.ChatCharacterGoal +import kr.co.vividnext.sodalive.chat.character.ChatCharacterHobby +import kr.co.vividnext.sodalive.chat.character.ChatCharacterTag +import kr.co.vividnext.sodalive.chat.character.ChatCharacterValue +import kr.co.vividnext.sodalive.chat.character.repository.ChatCharacterGoalRepository +import kr.co.vividnext.sodalive.chat.character.repository.ChatCharacterHobbyRepository +import kr.co.vividnext.sodalive.chat.character.repository.ChatCharacterRepository +import kr.co.vividnext.sodalive.chat.character.repository.ChatCharacterTagRepository +import kr.co.vividnext.sodalive.chat.character.repository.ChatCharacterValueRepository +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +@Service +class ChatCharacterService( + private val chatCharacterRepository: ChatCharacterRepository, + private val tagRepository: ChatCharacterTagRepository, + private val valueRepository: ChatCharacterValueRepository, + private val hobbyRepository: ChatCharacterHobbyRepository, + private val goalRepository: ChatCharacterGoalRepository +) { + + /** + * 태그를 찾거나 생성하여 캐릭터에 연결 + */ + @Transactional + fun addTagToCharacter(chatCharacter: ChatCharacter, tagName: String) { + val tag = tagRepository.findByTag(tagName) ?: ChatCharacterTag(tagName) + if (tag.id == null) { + tagRepository.save(tag) + } + chatCharacter.addTag(tag) + } + + /** + * 가치관을 찾거나 생성하여 캐릭터에 연결 + */ + @Transactional + fun addValueToCharacter(chatCharacter: ChatCharacter, valueName: String) { + val value = valueRepository.findByValue(valueName) ?: ChatCharacterValue(valueName) + if (value.id == null) { + valueRepository.save(value) + } + chatCharacter.addValue(value) + } + + /** + * 취미를 찾거나 생성하여 캐릭터에 연결 + */ + @Transactional + fun addHobbyToCharacter(chatCharacter: ChatCharacter, hobbyName: String) { + val hobby = hobbyRepository.findByHobby(hobbyName) ?: ChatCharacterHobby(hobbyName) + if (hobby.id == null) { + hobbyRepository.save(hobby) + } + chatCharacter.addHobby(hobby) + } + + /** + * 목표를 찾거나 생성하여 캐릭터에 연결 + */ + @Transactional + fun addGoalToCharacter(chatCharacter: ChatCharacter, goalName: String) { + val goal = goalRepository.findByGoal(goalName) ?: ChatCharacterGoal(goalName) + if (goal.id == null) { + goalRepository.save(goal) + } + chatCharacter.addGoal(goal) + } + + /** + * 여러 태그를 한번에 캐릭터에 연결 + */ + @Transactional + fun addTagsToCharacter(chatCharacter: ChatCharacter, tags: List) { + tags.forEach { addTagToCharacter(chatCharacter, it) } + } + + /** + * 여러 가치관을 한번에 캐릭터에 연결 + */ + @Transactional + fun addValuesToCharacter(chatCharacter: ChatCharacter, values: List) { + values.forEach { addValueToCharacter(chatCharacter, it) } + } + + /** + * 여러 취미를 한번에 캐릭터에 연결 + */ + @Transactional + fun addHobbiesToCharacter(chatCharacter: ChatCharacter, hobbies: List) { + hobbies.forEach { addHobbyToCharacter(chatCharacter, it) } + } + + /** + * 여러 목표를 한번에 캐릭터에 연결 + */ + @Transactional + fun addGoalsToCharacter(chatCharacter: ChatCharacter, goals: List) { + goals.forEach { addGoalToCharacter(chatCharacter, it) } + } + + /** + * 캐릭터 저장 + */ + @Transactional + fun saveChatCharacter(chatCharacter: ChatCharacter): ChatCharacter { + return chatCharacterRepository.save(chatCharacter) + } + + /** + * UUID로 캐릭터 조회 + */ + @Transactional(readOnly = true) + fun findByCharacterUUID(characterUUID: String): ChatCharacter? { + return chatCharacterRepository.findByCharacterUUID(characterUUID) + } + + /** + * 이름으로 캐릭터 조회 + */ + @Transactional(readOnly = true) + fun findByName(name: String): ChatCharacter? { + return chatCharacterRepository.findByName(name) + } + + /** + * 모든 캐릭터 조회 + */ + @Transactional(readOnly = true) + fun findAll(): List { + return chatCharacterRepository.findAll() + } + + /** + * 캐릭터 생성 및 관련 엔티티 연결 + */ + @Transactional + fun createChatCharacter( + characterUUID: String, + name: String, + description: String, + systemPrompt: String, + age: Int? = null, + gender: String? = null, + mbti: String? = null, + speechPattern: String? = null, + speechStyle: String? = null, + appearance: String? = null, + tags: List = emptyList(), + values: List = emptyList(), + hobbies: List = emptyList(), + goals: List = emptyList() + ): ChatCharacter { + val chatCharacter = ChatCharacter( + characterUUID = characterUUID, + name = name, + description = description, + systemPrompt = systemPrompt, + age = age, + gender = gender, + mbti = mbti, + speechPattern = speechPattern, + speechStyle = speechStyle, + appearance = appearance + ) + + // 관련 엔티티 연결 + addTagsToCharacter(chatCharacter, tags) + addValuesToCharacter(chatCharacter, values) + addHobbiesToCharacter(chatCharacter, hobbies) + addGoalsToCharacter(chatCharacter, goals) + + return saveChatCharacter(chatCharacter) + } + + /** + * 캐릭터에 기억 추가 + */ + @Transactional + fun addMemoryToChatCharacter(chatCharacter: ChatCharacter, title: String, content: String, emotion: String) { + chatCharacter.addMemory(title, content, emotion) + saveChatCharacter(chatCharacter) + } + + /** + * 캐릭터에 성격 특성 추가 + */ + @Transactional + fun addPersonalityToChatCharacter(chatCharacter: ChatCharacter, trait: String, description: String) { + chatCharacter.addPersonality(trait, description) + saveChatCharacter(chatCharacter) + } + + /** + * 캐릭터에 배경 정보 추가 + */ + @Transactional + fun addBackgroundToChatCharacter(chatCharacter: ChatCharacter, topic: String, description: String) { + chatCharacter.addBackground(topic, description) + saveChatCharacter(chatCharacter) + } + + /** + * 캐릭터에 관계 추가 + */ + @Transactional + fun addRelationshipToChatCharacter(chatCharacter: ChatCharacter, relationShip: String) { + chatCharacter.addRelationship(relationShip) + saveChatCharacter(chatCharacter) + } + + /** + * 캐릭터 생성 시 기본 정보와 함께 추가 정보도 설정 + */ + @Transactional + fun createChatCharacterWithDetails( + characterUUID: String, + name: String, + description: String, + systemPrompt: String, + age: Int? = null, + gender: String? = null, + mbti: String? = null, + speechPattern: String? = null, + speechStyle: String? = null, + appearance: String? = null, + tags: List = emptyList(), + values: List = emptyList(), + hobbies: List = emptyList(), + goals: List = emptyList(), + memories: List> = emptyList(), + personalities: List> = emptyList(), + backgrounds: List> = emptyList(), + relationships: List = emptyList() + ): ChatCharacter { + val chatCharacter = createChatCharacter( + characterUUID, name, description, systemPrompt, age, gender, mbti, + speechPattern, speechStyle, appearance, tags, values, hobbies, goals + ) + + // 추가 정보 설정 + memories.forEach { (title, content, emotion) -> + chatCharacter.addMemory(title, content, emotion) + } + + personalities.forEach { (trait, description) -> + chatCharacter.addPersonality(trait, description) + } + + backgrounds.forEach { (topic, description) -> + chatCharacter.addBackground(topic, description) + } + + relationships.forEach { relationShip -> + chatCharacter.addRelationship(relationShip) + } + + return saveChatCharacter(chatCharacter) + } +}