캐릭터 챗봇 #338
|
@ -61,16 +61,16 @@ class ChatCharacter(
|
||||||
) : BaseEntity() {
|
) : BaseEntity() {
|
||||||
var imagePath: String? = null
|
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()
|
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()
|
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()
|
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()
|
var relationships: MutableList<ChatCharacterRelationship> = mutableListOf()
|
||||||
|
|
||||||
@OneToMany(mappedBy = "chatCharacter", cascade = [CascadeType.ALL], fetch = FetchType.LAZY, orphanRemoval = true)
|
@OneToMany(mappedBy = "chatCharacter", cascade = [CascadeType.ALL], fetch = FetchType.LAZY, orphanRemoval = true)
|
||||||
|
|
|
@ -18,7 +18,7 @@ class ChatCharacterBackground(
|
||||||
|
|
||||||
// 배경 설명
|
// 배경 설명
|
||||||
@Column(columnDefinition = "TEXT", nullable = false)
|
@Column(columnDefinition = "TEXT", nullable = false)
|
||||||
val description: String,
|
var description: String,
|
||||||
|
|
||||||
@ManyToOne(fetch = FetchType.LAZY)
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
@JoinColumn(name = "chat_character_id")
|
@JoinColumn(name = "chat_character_id")
|
||||||
|
|
|
@ -18,10 +18,10 @@ class ChatCharacterMemory(
|
||||||
|
|
||||||
// 기억 내용
|
// 기억 내용
|
||||||
@Column(columnDefinition = "TEXT", nullable = false)
|
@Column(columnDefinition = "TEXT", nullable = false)
|
||||||
val content: String,
|
var content: String,
|
||||||
|
|
||||||
// 감정
|
// 감정
|
||||||
val emotion: String,
|
var emotion: String,
|
||||||
|
|
||||||
@ManyToOne(fetch = FetchType.LAZY)
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
@JoinColumn(name = "chat_character_id")
|
@JoinColumn(name = "chat_character_id")
|
||||||
|
|
|
@ -18,7 +18,7 @@ class ChatCharacterPersonality(
|
||||||
|
|
||||||
// 성격 특성 설명
|
// 성격 특성 설명
|
||||||
@Column(columnDefinition = "TEXT", nullable = false)
|
@Column(columnDefinition = "TEXT", nullable = false)
|
||||||
val description: String,
|
var description: String,
|
||||||
|
|
||||||
@ManyToOne(fetch = FetchType.LAZY)
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
@JoinColumn(name = "chat_character_id")
|
@JoinColumn(name = "chat_character_id")
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
package kr.co.vividnext.sodalive.chat.character.service
|
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.ChatCharacterRelationshipRequest
|
||||||
import kr.co.vividnext.sodalive.admin.chat.character.dto.ChatCharacterUpdateRequest
|
import kr.co.vividnext.sodalive.admin.chat.character.dto.ChatCharacterUpdateRequest
|
||||||
import kr.co.vividnext.sodalive.chat.character.CharacterType
|
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)
|
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)
|
return chatCharacterRepository.findByName(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 모든 캐릭터 조회
|
|
||||||
*/
|
|
||||||
@Transactional(readOnly = true)
|
|
||||||
fun findAll(): List<ChatCharacter> {
|
|
||||||
return chatCharacterRepository.findAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ID로 캐릭터 조회
|
* ID로 캐릭터 조회
|
||||||
*/
|
*/
|
||||||
|
@ -331,57 +459,6 @@ class ChatCharacterService(
|
||||||
return saveChatCharacter(chatCharacter)
|
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 imagePath 이미지 경로 (null이면 이미지 변경 없음)
|
||||||
* @param request 수정 요청 데이터 (id를 제외한 모든 필드는 null 가능)
|
* @param request 수정 요청 데이터 (id를 제외한 모든 필드는 null 가능)
|
||||||
* @return 수정된 ChatCharacter 객체
|
* @return 수정된 ChatCharacter 객체
|
||||||
* @throws SodaException 캐릭터를 찾을 수 없는 경우
|
|
||||||
*/
|
*/
|
||||||
@Transactional
|
@Transactional
|
||||||
fun updateChatCharacterWithDetails(
|
fun updateChatCharacterWithDetails(
|
||||||
|
@ -526,38 +602,19 @@ class ChatCharacterService(
|
||||||
|
|
||||||
// 추가 정보 설정 - 변경된 데이터만 업데이트
|
// 추가 정보 설정 - 변경된 데이터만 업데이트
|
||||||
if (request.memories != null) {
|
if (request.memories != null) {
|
||||||
chatCharacter.memories.clear()
|
updateMemoriesForCharacter(chatCharacter, request.memories)
|
||||||
request.memories.forEach { memory ->
|
|
||||||
chatCharacter.addMemory(memory.title, memory.content, memory.emotion)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.personalities != null) {
|
if (request.personalities != null) {
|
||||||
chatCharacter.personalities.clear()
|
updatePersonalitiesForCharacter(chatCharacter, request.personalities)
|
||||||
request.personalities.forEach { personality ->
|
|
||||||
chatCharacter.addPersonality(personality.trait, personality.description)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.backgrounds != null) {
|
if (request.backgrounds != null) {
|
||||||
chatCharacter.backgrounds.clear()
|
updateBackgroundsForCharacter(chatCharacter, request.backgrounds)
|
||||||
request.backgrounds.forEach { background ->
|
|
||||||
chatCharacter.addBackground(background.topic, background.description)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.relationships != null) {
|
if (request.relationships != null) {
|
||||||
chatCharacter.relationships.clear()
|
updateRelationshipsForCharacter(chatCharacter, request.relationships)
|
||||||
request.relationships.forEach { rr ->
|
|
||||||
chatCharacter.addRelationship(
|
|
||||||
rr.personName,
|
|
||||||
rr.relationshipName,
|
|
||||||
rr.description,
|
|
||||||
rr.importance,
|
|
||||||
rr.relationshipType,
|
|
||||||
rr.currentStatus
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return saveChatCharacter(chatCharacter)
|
return saveChatCharacter(chatCharacter)
|
||||||
|
|
Loading…
Reference in New Issue