From 1a5b4ccb96a9befdb30b98040f1c75673091255f Mon Sep 17 00:00:00 2001 From: rivkode Date: Sun, 26 Apr 2026 16:30:10 +0900 Subject: [PATCH] feat(ai): add transcript-aware chat prompts and request type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add AiChatCommand.AiRequest.TranscriptChatRequest carrying conversation history plus video transcript context - AiChatService gains greetingTranscriptChat / generateTranscriptChat that use the new request type and persist the AI response with the right ChatRoom and SenderType.AI - AiPromptService loads two new prompt resources and renders the system/user messages with persona, history, and transcript_context - Add prompt resources: - prompts/system-transcript-chat-message.st (Korean "자모" persona that must stay grounded in the transcript) - prompts/user-transcript-chat-message.st (transcript_context + history placeholders only) No secret values introduced; prompt resources contain only generic guidance text. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../language/domain/ai/AiChatCommand.kt | 11 +++++ .../language/domain/ai/AiChatService.kt | 44 +++++++++++++++++++ .../language/domain/ai/AiPromptService.kt | 32 ++++++++++++++ .../prompts/system-transcript-chat-message.st | 16 +++++++ .../prompts/user-transcript-chat-message.st | 9 ++++ 5 files changed, 112 insertions(+) create mode 100644 src/main/resources/prompts/system-transcript-chat-message.st create mode 100644 src/main/resources/prompts/user-transcript-chat-message.st diff --git a/src/main/kotlin/com/learner/language/domain/ai/AiChatCommand.kt b/src/main/kotlin/com/learner/language/domain/ai/AiChatCommand.kt index 8bf925e..a0b38f5 100644 --- a/src/main/kotlin/com/learner/language/domain/ai/AiChatCommand.kt +++ b/src/main/kotlin/com/learner/language/domain/ai/AiChatCommand.kt @@ -33,6 +33,17 @@ class AiChatCommand { } } + data class TranscriptChatRequest( + val history: String, + val transcriptContext: String, + override val personaType: PersonaType, + override val input: String = history, + ) : AiRequest(input, personaType) { + override fun createPrompt(aiPromptService: AiPromptService): Prompt { + return aiPromptService.createPrompt(this) + } + } + data class ChatRoomNameRequest( override val input: String, override val personaType: PersonaType, diff --git a/src/main/kotlin/com/learner/language/domain/ai/AiChatService.kt b/src/main/kotlin/com/learner/language/domain/ai/AiChatService.kt index 2a46ccc..0e1297b 100644 --- a/src/main/kotlin/com/learner/language/domain/ai/AiChatService.kt +++ b/src/main/kotlin/com/learner/language/domain/ai/AiChatService.kt @@ -76,6 +76,25 @@ class AiChatService( return chatMessage } + fun generateTranscriptChat( + command: ChatCommand.Generate, + user: User, + chatRoom: ChatRoom, + chatHistory: String, + transcriptContext: String, + nextSequence: Int + ): ChatMessage { + val aiRequest = AiChatCommand.AiRequest.TranscriptChatRequest( + history = chatHistory, + transcriptContext = transcriptContext, + personaType = command.personaType + ) + val response = generate(aiRequest) + logger.info { "ai answer response generateTranscriptChat: ${response.response}" } + + return command.toEntity(user, chatRoom, response.response, nextSequence) + } + fun greetingChat(personaType: PersonaType, user: User, chatRoom: ChatRoom, chatHistory: String, nextSequence: Int): ChatMessage { val aiRequest = AiChatCommand.AiRequest.ChatRequest( input = chatHistory, @@ -88,6 +107,31 @@ class AiChatService( return chatMessage } + fun greetingTranscriptChat( + personaType: PersonaType, + user: User, + chatRoom: ChatRoom, + chatHistory: String, + transcriptContext: String, + nextSequence: Int + ): ChatMessage { + val aiRequest = AiChatCommand.AiRequest.TranscriptChatRequest( + history = chatHistory, + transcriptContext = transcriptContext, + personaType = personaType + ) + val response = generateGreeting(aiRequest) + logger.info { "ai answer response greetingTranscriptChat: ${response.response}" } + + return ChatMessage( + user = user, + chatRoom = chatRoom, + message = response.response, + senderType = SenderType.AI, + sequence = nextSequence + ) + } + fun phraseChat( personaType: PersonaType, user: User, diff --git a/src/main/kotlin/com/learner/language/domain/ai/AiPromptService.kt b/src/main/kotlin/com/learner/language/domain/ai/AiPromptService.kt index eed9203..0de5c57 100644 --- a/src/main/kotlin/com/learner/language/domain/ai/AiPromptService.kt +++ b/src/main/kotlin/com/learner/language/domain/ai/AiPromptService.kt @@ -16,6 +16,8 @@ class AiPromptService( private val systemFeedbackResource: Resource, @Value("classpath:prompts/system-chat-message-0112-gpt.st") // gpt private val systemChatResource: Resource, + @Value("classpath:prompts/system-transcript-chat-message.st") + private val systemTranscriptChatResource: Resource, @Value("classpath:prompts/system-phrase-message.st") private val systemPhraseResource: Resource, @Value("classpath:prompts/system-chatroomname-message.st") @@ -24,6 +26,8 @@ class AiPromptService( private val userFeedbackResource: Resource, @Value("classpath:prompts/user-chat-message.st") private val userChatResource: Resource, + @Value("classpath:prompts/user-transcript-chat-message.st") + private val userTranscriptChatResource: Resource, @Value("classpath:prompts/user-phrase-message.st") private val userPhraseResource: Resource, @Value("classpath:prompts/user-chatroomname-message.st") @@ -58,6 +62,15 @@ class AiPromptService( ) } + private fun createUserTranscriptChatMessage(history: String, transcriptContext: String): Message { + return PromptTemplate(userTranscriptChatResource).createMessage( + mapOf( + "history" to history, + "transcript_context" to transcriptContext, + ) + ) + } + private fun createUserChatRoomNameMessage(message: String): Message { return PromptTemplate(userChatRoomNameResource).createMessage( mapOf( @@ -82,6 +95,13 @@ class AiPromptService( ) ) + private fun createSystemTranscriptChatMessage(personaType: PersonaType): Message = + SystemPromptTemplate(systemTranscriptChatResource).createMessage( + mapOf( + "persona" to toKoreanPersona(personaType) + ) + ) + private fun createSystemChatRoomNameMessage(): Message = SystemPromptTemplate(systemChatRoomNameResource).createMessage( ) @@ -116,6 +136,18 @@ class AiPromptService( ) } + is AiChatCommand.AiRequest.TranscriptChatRequest -> { + Prompt( + listOf( + createUserTranscriptChatMessage( + aiChatRequest.history, + aiChatRequest.transcriptContext + ), + createSystemTranscriptChatMessage(aiChatRequest.personaType) + ) + ) + } + is AiChatCommand.AiRequest.ChatRoomNameRequest -> { Prompt( listOf( diff --git a/src/main/resources/prompts/system-transcript-chat-message.st b/src/main/resources/prompts/system-transcript-chat-message.st new file mode 100644 index 0000000..d3bbc82 --- /dev/null +++ b/src/main/resources/prompts/system-transcript-chat-message.st @@ -0,0 +1,16 @@ +Role: +You are a Korean native-speaking AI conversation partner named "자모". +You speak in a natural Korean tone with this persona style: {persona} + +Primary Goal: +- Talk with the user based on the provided video transcript context. +- Use the transcript as the main source of truth for topic, expressions, and situation. + +Rules: +- Stay grounded in the transcript context and recent conversation history. +- If the user asks about a line, expression, or situation from the video, explain it clearly in Korean. +- If the transcript does not support a claim, say it is uncertain instead of making it up. +- Keep the conversation natural, not overly academic. +- You may help the user practice Korean expressions that appear in the transcript. +- Do not prefix responses with your name. +- Output Korean only. diff --git a/src/main/resources/prompts/user-transcript-chat-message.st b/src/main/resources/prompts/user-transcript-chat-message.st new file mode 100644 index 0000000..a46ce42 --- /dev/null +++ b/src/main/resources/prompts/user-transcript-chat-message.st @@ -0,0 +1,9 @@ +Transcript Context: +""" +{transcript_context} +""" + +Conversation History: +""" +{history} +"""