From 899f2865b31fe480523a1f7241f5adc948ac5231 Mon Sep 17 00:00:00 2001 From: Klaus Date: Wed, 26 Nov 2025 11:40:58 +0900 Subject: [PATCH] =?UTF-8?q?feat(chat-character):=20=EC=BA=90=EB=A6=AD?= =?UTF-8?q?=ED=84=B0=20=EB=93=B1=EB=A1=9D=EC=8B=9C=20=ED=8C=8C=ED=8C=8C?= =?UTF-8?q?=EA=B3=A0=20=EC=96=B8=EC=96=B4=20=EA=B0=90=EC=A7=80=20API?= =?UTF-8?q?=EB=A5=BC=20=ED=98=B8=EC=B6=9C=ED=95=98=EC=97=AC=20languageCode?= =?UTF-8?q?=EB=A5=BC=20=EA=B8=B0=EB=A1=9D=ED=95=98=EB=8A=94=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../character/AdminChatCharacterController.kt | 16 +++++++++ .../sodalive/chat/character/ChatCharacter.kt | 2 ++ .../sodalive/content/LanguageDetectEvent.kt | 35 +++++++++++++++++++ 3 files changed, 53 insertions(+) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/character/AdminChatCharacterController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/character/AdminChatCharacterController.kt index dfa8c3a..21a7bda 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/character/AdminChatCharacterController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/chat/character/AdminChatCharacterController.kt @@ -13,8 +13,11 @@ import kr.co.vividnext.sodalive.chat.character.CharacterType import kr.co.vividnext.sodalive.chat.character.service.ChatCharacterService import kr.co.vividnext.sodalive.common.ApiResponse import kr.co.vividnext.sodalive.common.SodaException +import kr.co.vividnext.sodalive.content.LanguageDetectEvent +import kr.co.vividnext.sodalive.content.LanguageDetectTargetType import kr.co.vividnext.sodalive.utils.generateFileName import org.springframework.beans.factory.annotation.Value +import org.springframework.context.ApplicationEventPublisher import org.springframework.http.HttpEntity import org.springframework.http.HttpHeaders import org.springframework.http.HttpMethod @@ -40,6 +43,7 @@ class AdminChatCharacterController( private val adminService: AdminChatCharacterService, private val s3Uploader: S3Uploader, private val originalWorkService: AdminOriginalWorkService, + private val applicationEventPublisher: ApplicationEventPublisher, @Value("\${weraser.api-key}") private val apiKey: String, @@ -165,6 +169,18 @@ class AdminChatCharacterController( originalWorkService.assignOneCharacter(request.originalWorkId, chatCharacter.id!!) } + // 5. 언어 코드가 지정되지 않은 경우, 파파고 언어 감지 API를 통해 비동기로 언어를 식별한다. + // 언어 감지에 사용할 내용은 chatCharacter.description 만 사용한다. + if (chatCharacter.languageCode.isNullOrBlank() && chatCharacter.description.isNotBlank()) { + applicationEventPublisher.publishEvent( + LanguageDetectEvent( + id = chatCharacter.id!!, + query = chatCharacter.description, + targetType = LanguageDetectTargetType.CHARACTER + ) + ) + } + ApiResponse.ok(null) } 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 index 981b6f1..5a62851 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/ChatCharacter.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/ChatCharacter.kt @@ -22,6 +22,8 @@ class ChatCharacter( // 캐릭터 한 줄 소개 var description: String, + var languageCode: String? = null, + // AI 시스템 프롬프트 @Column(columnDefinition = "TEXT", nullable = false) var systemPrompt: String, diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/content/LanguageDetectEvent.kt b/src/main/kotlin/kr/co/vividnext/sodalive/content/LanguageDetectEvent.kt index feb06c6..64a3bfb 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/content/LanguageDetectEvent.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/content/LanguageDetectEvent.kt @@ -1,6 +1,7 @@ package kr.co.vividnext.sodalive.content import kr.co.vividnext.sodalive.chat.character.comment.CharacterCommentRepository +import kr.co.vividnext.sodalive.chat.character.repository.ChatCharacterRepository import kr.co.vividnext.sodalive.content.comment.AudioContentCommentRepository import kr.co.vividnext.sodalive.explorer.profile.CreatorCheersRepository import org.slf4j.LoggerFactory @@ -23,6 +24,7 @@ import org.springframework.web.client.RestTemplate enum class LanguageDetectTargetType { CONTENT, COMMENT, + CHARACTER, CHARACTER_COMMENT, CREATOR_CHEERS } @@ -41,6 +43,7 @@ data class PapagoLanguageDetectResponse( class LanguageDetectListener( private val audioContentRepository: AudioContentRepository, private val audioContentCommentRepository: AudioContentCommentRepository, + private val chatCharacterRepository: ChatCharacterRepository, private val characterCommentRepository: CharacterCommentRepository, private val creatorCheersRepository: CreatorCheersRepository, @@ -69,11 +72,43 @@ class LanguageDetectListener( when (event.targetType) { LanguageDetectTargetType.CONTENT -> handleContentLanguageDetect(event) LanguageDetectTargetType.COMMENT -> handleCommentLanguageDetect(event) + LanguageDetectTargetType.CHARACTER -> handleCharacterLanguageDetect(event) LanguageDetectTargetType.CHARACTER_COMMENT -> handleCharacterCommentLanguageDetect(event) LanguageDetectTargetType.CREATOR_CHEERS -> handleCreatorCheersLanguageDetect(event) } } + private fun handleCharacterLanguageDetect(event: LanguageDetectEvent) { + val characterId = event.id + + val character = chatCharacterRepository.findById(characterId).orElse(null) + if (character == null) { + log.warn("[PapagoLanguageDetect] ChatCharacter not found. characterId={}", characterId) + return + } + + // 이미 언어 코드가 설정된 경우 호출하지 않음 + if (!character.languageCode.isNullOrBlank()) { + log.debug( + "[PapagoLanguageDetect] languageCode already set. Skip language detection. characterId={}, languageCode={}", + characterId, + character.languageCode + ) + return + } + + val langCode = requestPapagoLanguageCode(event.query, characterId) ?: return + + character.languageCode = langCode + chatCharacterRepository.save(character) + + log.info( + "[PapagoLanguageDetect] languageCode updated from Papago. characterId={}, langCode={}", + characterId, + langCode + ) + } + private fun handleContentLanguageDetect(event: LanguageDetectEvent) { val contentId = event.id