캐릭터 챗봇 #338
|
@ -61,16 +61,16 @@ class ChatCharacter(
|
|||
) : BaseEntity() {
|
||||
var imagePath: String? = null
|
||||
|
||||
@OneToMany(mappedBy = "chatCharacter", cascade = [CascadeType.ALL], fetch = FetchType.LAZY)
|
||||
@OneToMany(mappedBy = "chatCharacter", cascade = [CascadeType.ALL], fetch = FetchType.LAZY, orphanRemoval = true)
|
||||
var memories: MutableList<ChatCharacterMemory> = mutableListOf()
|
||||
|
||||
@OneToMany(mappedBy = "chatCharacter", cascade = [CascadeType.ALL], fetch = FetchType.LAZY)
|
||||
@OneToMany(mappedBy = "chatCharacter", cascade = [CascadeType.ALL], fetch = FetchType.LAZY, orphanRemoval = true)
|
||||
var personalities: MutableList<ChatCharacterPersonality> = mutableListOf()
|
||||
|
||||
@OneToMany(mappedBy = "chatCharacter", cascade = [CascadeType.ALL], fetch = FetchType.LAZY)
|
||||
@OneToMany(mappedBy = "chatCharacter", cascade = [CascadeType.ALL], fetch = FetchType.LAZY, orphanRemoval = true)
|
||||
var backgrounds: MutableList<ChatCharacterBackground> = mutableListOf()
|
||||
|
||||
@OneToMany(mappedBy = "chatCharacter", cascade = [CascadeType.ALL], fetch = FetchType.LAZY)
|
||||
@OneToMany(mappedBy = "chatCharacter", cascade = [CascadeType.ALL], fetch = FetchType.LAZY, orphanRemoval = true)
|
||||
var relationships: MutableList<ChatCharacterRelationship> = mutableListOf()
|
||||
|
||||
@OneToMany(mappedBy = "chatCharacter", cascade = [CascadeType.ALL], fetch = FetchType.LAZY, orphanRemoval = true)
|
||||
|
|
|
@ -18,7 +18,7 @@ class ChatCharacterBackground(
|
|||
|
||||
// 배경 설명
|
||||
@Column(columnDefinition = "TEXT", nullable = false)
|
||||
val description: String,
|
||||
var description: String,
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "chat_character_id")
|
||||
|
|
|
@ -18,10 +18,10 @@ class ChatCharacterMemory(
|
|||
|
||||
// 기억 내용
|
||||
@Column(columnDefinition = "TEXT", nullable = false)
|
||||
val content: String,
|
||||
var content: String,
|
||||
|
||||
// 감정
|
||||
val emotion: String,
|
||||
var emotion: String,
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "chat_character_id")
|
||||
|
|
|
@ -18,7 +18,7 @@ class ChatCharacterPersonality(
|
|||
|
||||
// 성격 특성 설명
|
||||
@Column(columnDefinition = "TEXT", nullable = false)
|
||||
val description: String,
|
||||
var description: String,
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "chat_character_id")
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
package kr.co.vividnext.sodalive.chat.character.service
|
||||
|
||||
import kr.co.vividnext.sodalive.admin.chat.character.dto.ChatCharacterBackgroundRequest
|
||||
import kr.co.vividnext.sodalive.admin.chat.character.dto.ChatCharacterMemoryRequest
|
||||
import kr.co.vividnext.sodalive.admin.chat.character.dto.ChatCharacterPersonalityRequest
|
||||
import kr.co.vividnext.sodalive.admin.chat.character.dto.ChatCharacterRelationshipRequest
|
||||
import kr.co.vividnext.sodalive.admin.chat.character.dto.ChatCharacterUpdateRequest
|
||||
import kr.co.vividnext.sodalive.chat.character.CharacterType
|
||||
|
@ -222,6 +225,147 @@ class ChatCharacterService(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 기억(memories) 증분 업데이트
|
||||
*/
|
||||
@Transactional
|
||||
fun updateMemoriesForCharacter(chatCharacter: ChatCharacter, memories: List<ChatCharacterMemoryRequest>) {
|
||||
val desiredByTitle = memories
|
||||
.asSequence()
|
||||
.distinctBy { it.title }
|
||||
.associateBy { it.title }
|
||||
|
||||
val iterator = chatCharacter.memories.iterator()
|
||||
while (iterator.hasNext()) {
|
||||
val current = iterator.next()
|
||||
val desired = desiredByTitle[current.title]
|
||||
if (desired == null) {
|
||||
// 요청에 없는 항목은 제거
|
||||
iterator.remove()
|
||||
} else {
|
||||
// 값 필드만 in-place 업데이트
|
||||
if (current.content != desired.content) current.content = desired.content
|
||||
if (current.emotion != desired.emotion) current.emotion = desired.emotion
|
||||
}
|
||||
}
|
||||
|
||||
// 신규 추가
|
||||
val existingTitles = chatCharacter.memories.map { it.title }.toSet()
|
||||
desiredByTitle.values
|
||||
.filterNot { existingTitles.contains(it.title) }
|
||||
.forEach { chatCharacter.addMemory(it.title, it.content, it.emotion) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 성격(personalities) 증분 업데이트
|
||||
*/
|
||||
@Transactional
|
||||
fun updatePersonalitiesForCharacter(
|
||||
chatCharacter: ChatCharacter,
|
||||
personalities: List<ChatCharacterPersonalityRequest>
|
||||
) {
|
||||
val desiredByTrait = personalities
|
||||
.asSequence()
|
||||
.distinctBy { it.trait }
|
||||
.associateBy { it.trait }
|
||||
|
||||
val iterator = chatCharacter.personalities.iterator()
|
||||
while (iterator.hasNext()) {
|
||||
val current = iterator.next()
|
||||
val desired = desiredByTrait[current.trait]
|
||||
if (desired == null) {
|
||||
// 요청에 없는 항목은 제거
|
||||
iterator.remove()
|
||||
} else {
|
||||
// 값 필드만 in-place 업데이트
|
||||
if (current.description != desired.description) current.description = desired.description
|
||||
}
|
||||
}
|
||||
|
||||
// 신규 추가
|
||||
val existingTraits = chatCharacter.personalities.map { it.trait }.toSet()
|
||||
desiredByTrait.values
|
||||
.filterNot { existingTraits.contains(it.trait) }
|
||||
.forEach { chatCharacter.addPersonality(it.trait, it.description) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 배경(backgrounds) 증분 업데이트
|
||||
*/
|
||||
@Transactional
|
||||
fun updateBackgroundsForCharacter(chatCharacter: ChatCharacter, backgrounds: List<ChatCharacterBackgroundRequest>) {
|
||||
val desiredByTopic = backgrounds
|
||||
.asSequence()
|
||||
.distinctBy { it.topic }
|
||||
.associateBy { it.topic }
|
||||
|
||||
val iterator = chatCharacter.backgrounds.iterator()
|
||||
while (iterator.hasNext()) {
|
||||
val current = iterator.next()
|
||||
val desired = desiredByTopic[current.topic]
|
||||
if (desired == null) {
|
||||
// 요청에 없는 항목은 제거
|
||||
iterator.remove()
|
||||
} else {
|
||||
// 값 필드만 in-place 업데이트
|
||||
if (current.description != desired.description) current.description = desired.description
|
||||
}
|
||||
}
|
||||
|
||||
// 신규 추가
|
||||
val existingTopics = chatCharacter.backgrounds.map { it.topic }.toSet()
|
||||
desiredByTopic.values
|
||||
.filterNot { existingTopics.contains(it.topic) }
|
||||
.forEach { chatCharacter.addBackground(it.topic, it.description) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 관계(relationships) 증분 업데이트
|
||||
*/
|
||||
@Transactional
|
||||
fun updateRelationshipsForCharacter(
|
||||
chatCharacter: ChatCharacter,
|
||||
relationships: List<ChatCharacterRelationshipRequest>
|
||||
) {
|
||||
fun keyOf(p: String, r: String) = "$" + "{" + p + "}" + "::" + "{" + r + "}"
|
||||
|
||||
val desiredByKey = relationships
|
||||
.asSequence()
|
||||
.distinctBy { keyOf(it.personName, it.relationshipName) }
|
||||
.associateBy { keyOf(it.personName, it.relationshipName) }
|
||||
|
||||
val iterator = chatCharacter.relationships.iterator()
|
||||
while (iterator.hasNext()) {
|
||||
val current = iterator.next()
|
||||
val key = keyOf(current.personName, current.relationshipName)
|
||||
val desired = desiredByKey[key]
|
||||
if (desired == null) {
|
||||
iterator.remove()
|
||||
} else {
|
||||
if (current.description != desired.description) current.description = desired.description
|
||||
if (current.importance != desired.importance) current.importance = desired.importance
|
||||
if (current.relationshipType != desired.relationshipType) {
|
||||
current.relationshipType = desired.relationshipType
|
||||
}
|
||||
if (current.currentStatus != desired.currentStatus) current.currentStatus = desired.currentStatus
|
||||
}
|
||||
}
|
||||
|
||||
val existingKeys = chatCharacter.relationships.map { keyOf(it.personName, it.relationshipName) }.toSet()
|
||||
desiredByKey.values
|
||||
.filterNot { existingKeys.contains(keyOf(it.personName, it.relationshipName)) }
|
||||
.forEach { rr ->
|
||||
chatCharacter.addRelationship(
|
||||
rr.personName,
|
||||
rr.relationshipName,
|
||||
rr.description,
|
||||
rr.importance,
|
||||
rr.relationshipType,
|
||||
rr.currentStatus
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 캐릭터 저장
|
||||
*/
|
||||
|
@ -230,14 +374,6 @@ class ChatCharacterService(
|
|||
return chatCharacterRepository.save(chatCharacter)
|
||||
}
|
||||
|
||||
/**
|
||||
* UUID로 캐릭터 조회
|
||||
*/
|
||||
@Transactional(readOnly = true)
|
||||
fun findByCharacterUUID(characterUUID: String): ChatCharacter? {
|
||||
return chatCharacterRepository.findByCharacterUUID(characterUUID)
|
||||
}
|
||||
|
||||
/**
|
||||
* 이름으로 캐릭터 조회
|
||||
*/
|
||||
|
@ -246,14 +382,6 @@ class ChatCharacterService(
|
|||
return chatCharacterRepository.findByName(name)
|
||||
}
|
||||
|
||||
/**
|
||||
* 모든 캐릭터 조회
|
||||
*/
|
||||
@Transactional(readOnly = true)
|
||||
fun findAll(): List<ChatCharacter> {
|
||||
return chatCharacterRepository.findAll()
|
||||
}
|
||||
|
||||
/**
|
||||
* ID로 캐릭터 조회
|
||||
*/
|
||||
|
@ -331,57 +459,6 @@ class ChatCharacterService(
|
|||
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,
|
||||
personName: String,
|
||||
relationshipName: String,
|
||||
description: String,
|
||||
importance: Int,
|
||||
relationshipType: String,
|
||||
currentStatus: String
|
||||
) {
|
||||
chatCharacter.addRelationship(
|
||||
personName,
|
||||
relationshipName,
|
||||
description,
|
||||
importance,
|
||||
relationshipType,
|
||||
currentStatus
|
||||
)
|
||||
saveChatCharacter(chatCharacter)
|
||||
}
|
||||
|
||||
/**
|
||||
* 캐릭터 생성 시 기본 정보와 함께 추가 정보도 설정
|
||||
*/
|
||||
|
@ -464,7 +541,6 @@ class ChatCharacterService(
|
|||
* @param imagePath 이미지 경로 (null이면 이미지 변경 없음)
|
||||
* @param request 수정 요청 데이터 (id를 제외한 모든 필드는 null 가능)
|
||||
* @return 수정된 ChatCharacter 객체
|
||||
* @throws SodaException 캐릭터를 찾을 수 없는 경우
|
||||
*/
|
||||
@Transactional
|
||||
fun updateChatCharacterWithDetails(
|
||||
|
@ -526,38 +602,19 @@ class ChatCharacterService(
|
|||
|
||||
// 추가 정보 설정 - 변경된 데이터만 업데이트
|
||||
if (request.memories != null) {
|
||||
chatCharacter.memories.clear()
|
||||
request.memories.forEach { memory ->
|
||||
chatCharacter.addMemory(memory.title, memory.content, memory.emotion)
|
||||
}
|
||||
updateMemoriesForCharacter(chatCharacter, request.memories)
|
||||
}
|
||||
|
||||
if (request.personalities != null) {
|
||||
chatCharacter.personalities.clear()
|
||||
request.personalities.forEach { personality ->
|
||||
chatCharacter.addPersonality(personality.trait, personality.description)
|
||||
}
|
||||
updatePersonalitiesForCharacter(chatCharacter, request.personalities)
|
||||
}
|
||||
|
||||
if (request.backgrounds != null) {
|
||||
chatCharacter.backgrounds.clear()
|
||||
request.backgrounds.forEach { background ->
|
||||
chatCharacter.addBackground(background.topic, background.description)
|
||||
}
|
||||
updateBackgroundsForCharacter(chatCharacter, request.backgrounds)
|
||||
}
|
||||
|
||||
if (request.relationships != null) {
|
||||
chatCharacter.relationships.clear()
|
||||
request.relationships.forEach { rr ->
|
||||
chatCharacter.addRelationship(
|
||||
rr.personName,
|
||||
rr.relationshipName,
|
||||
rr.description,
|
||||
rr.importance,
|
||||
rr.relationshipType,
|
||||
rr.currentStatus
|
||||
)
|
||||
}
|
||||
updateRelationshipsForCharacter(chatCharacter, request.relationships)
|
||||
}
|
||||
|
||||
return saveChatCharacter(chatCharacter)
|
||||
|
|
Loading…
Reference in New Issue