From aec426eaa7b80518e73259b04df521fce04b4795 Mon Sep 17 00:00:00 2001 From: GerHerMo Date: Thu, 9 Oct 2025 18:11:26 +0900 Subject: [PATCH 1/4] feat: QA guide msg add --- .../chatbot/service/ChatbotService.java | 76 +++++++++++++++---- 1 file changed, 62 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/back/domain/chatbot/service/ChatbotService.java b/src/main/java/com/back/domain/chatbot/service/ChatbotService.java index e364df3..3dca13e 100644 --- a/src/main/java/com/back/domain/chatbot/service/ChatbotService.java +++ b/src/main/java/com/back/domain/chatbot/service/ChatbotService.java @@ -95,9 +95,55 @@ public ChatResponseDto sendMessage(ChatRequestDto requestDto) { log.info("[EXPLICIT] currentStep={}, userId={}, mode={}", currentStep, requestDto.getUserId(), currentStep == 0 ? "QA" : "STEP"); - if (currentStep == 0) { - // 질문형 추천 (일반 AI 대화) + // 질문형 추천 선택 시 안내 메시지와 INPUT 타입 반환 + if ("QA".equalsIgnoreCase(requestDto.getMessage()) || + requestDto.getMessage().contains("질문형")) { + + log.info("질문형 추천 시작 - userId: {}", requestDto.getUserId()); + + // 사용자 선택 메시지 저장 + ChatConversation userChoice = ChatConversation.builder() + .userId(requestDto.getUserId()) + .message("질문형 취향 찾기") + .sender(MessageSender.USER) + .createdAt(LocalDateTime.now()) + .build(); + chatConversationRepository.save(userChoice); + + String guideMessage = "칵테일에 관련된 질문을 입력해주세요!"; + + /* + + String guideMessage = "좋아요! 질문형 추천을 시작할게요 🎯\n" + + "칵테일에 관련된 질문을 자유롭게 입력해주세요!\n" + + "예시: 달콤한 칵테일 추천해줘, 파티용 칵테일이 필요해, 초보자용 칵테일 알려줘"; + */ + + ChatConversation botGuide = ChatConversation.builder() + .userId(requestDto.getUserId()) + .message(guideMessage) + .sender(MessageSender.CHATBOT) + .createdAt(LocalDateTime.now()) + .build(); + ChatConversation savedGuide = chatConversationRepository.save(botGuide); + + // INPUT 타입으로 반환하여 사용자 입력 유도 + return ChatResponseDto.builder() + .id(savedGuide.getId()) + .userId(requestDto.getUserId()) + .message(guideMessage) + .sender(MessageSender.CHATBOT) + .type(MessageType.INPUT) + .createdAt(savedGuide.getCreatedAt()) + .metaData(ChatResponseDto.MetaData.builder() + .currentStep(0) + .actionType("질문형 추천") + .build()) + .build(); + } + + // 실제 질문이 들어온 경우 AI 응답 생성 log.info("질문형 추천 모드 진입 - userId: {}", requestDto.getUserId()); return generateAIResponseWithContext(requestDto, "질문형 추천"); } @@ -473,10 +519,22 @@ private ChatResponseDto createErrorResponse(String errorMessage) { private ChatResponseDto handleStepRecommendation(ChatRequestDto requestDto) { Integer currentStep = requestDto.getCurrentStep(); + + // 단계별 추천 선택 시 처리 + if (currentStep == 1 && "STEP".equalsIgnoreCase(requestDto.getMessage())) { + // 사용자 선택 메시지 저장 + ChatConversation userChoice = ChatConversation.builder() + .userId(requestDto.getUserId()) + .message("단계별 취향 찾기") + .sender(MessageSender.USER) + .createdAt(LocalDateTime.now()) + .build(); + chatConversationRepository.save(userChoice); + } + if (currentStep == null || currentStep <= 0) { currentStep = 1; } - StepRecommendationResponseDto stepData; String message; MessageType type; @@ -521,17 +579,7 @@ private ChatResponseDto handleStepRecommendation(ChatRequestDto requestDto) { message = "단계별 맞춤 취향 추천을 시작합니다! 🎯"; type = MessageType.RADIO_OPTIONS; } - - // 사용자 메시지 저장 (단계별 추천 요청) - ChatConversation userMessage = ChatConversation.builder() - .userId(requestDto.getUserId()) - .message(requestDto.getMessage()) - .sender(MessageSender.USER) - .createdAt(LocalDateTime.now()) - .build(); - chatConversationRepository.save(userMessage); - - // 봇 응답 저장 + // 봇 응답 저장 (사용자 메시지는 이미 위에서 저장) ChatConversation botResponse = ChatConversation.builder() .userId(requestDto.getUserId()) .message(message) From af9b3dcfa12fe2d507d61ca55d8a92075a19a0e1 Mon Sep 17 00:00:00 2001 From: GerHerMo Date: Thu, 9 Oct 2025 23:23:37 +0900 Subject: [PATCH 2/4] feat: STEP loading msg --- .../chatbot/service/ChatbotService.java | 119 ++++++++++++++++-- 1 file changed, 107 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/back/domain/chatbot/service/ChatbotService.java b/src/main/java/com/back/domain/chatbot/service/ChatbotService.java index 3dca13e..1d92cfa 100644 --- a/src/main/java/com/back/domain/chatbot/service/ChatbotService.java +++ b/src/main/java/com/back/domain/chatbot/service/ChatbotService.java @@ -62,6 +62,14 @@ public class ChatbotService { private String responseRules; private ChatClient chatClient; + // 로딩 메시지 상수 + private static final String RECOMMENDATION_LOADING_MESSAGE = + "당신에게 어울리는 칵테일은? 두구❤️두구💛두구💚두구💙두구💜두구🖤두구🤍두구🤎"; + + // 처리 완료 플래그 키워드 + private static final String PROCESS_STEP_RECOMMENDATION = "PROCESS_STEP_RECOMMENDATION"; + private static final String PROCESS_QA_RECOMMENDATION = "PROCESS_QA_RECOMMENDATION"; + @PostConstruct public void init() throws IOException { this.systemPrompt = StreamUtils.copyToString( @@ -95,6 +103,7 @@ public ChatResponseDto sendMessage(ChatRequestDto requestDto) { log.info("[EXPLICIT] currentStep={}, userId={}, mode={}", currentStep, requestDto.getUserId(), currentStep == 0 ? "QA" : "STEP"); + if (currentStep == 0) { // 질문형 추천 선택 시 안내 메시지와 INPUT 타입 반환 if ("QA".equalsIgnoreCase(requestDto.getMessage()) || @@ -112,9 +121,7 @@ public ChatResponseDto sendMessage(ChatRequestDto requestDto) { chatConversationRepository.save(userChoice); String guideMessage = "칵테일에 관련된 질문을 입력해주세요!"; - /* - String guideMessage = "좋아요! 질문형 추천을 시작할게요 🎯\n" + "칵테일에 관련된 질문을 자유롭게 입력해주세요!\n" + "예시: 달콤한 칵테일 추천해줘, 파티용 칵테일이 필요해, 초보자용 칵테일 알려줘"; @@ -143,9 +150,50 @@ public ChatResponseDto sendMessage(ChatRequestDto requestDto) { .build(); } - // 실제 질문이 들어온 경우 AI 응답 생성 - log.info("질문형 추천 모드 진입 - userId: {}", requestDto.getUserId()); - return generateAIResponseWithContext(requestDto, "질문형 추천"); + // 실제 질문이 들어온 경우 - 먼저 로딩 메시지 반환 + if (requestDto.getMessage() != null && !requestDto.getMessage().trim().isEmpty()) { + // 로딩 메시지인지 확인 (두구두구 메시지 이후의 실제 처리 요청) + if (requestDto.getMessage().contains("PROCESS_RECOMMENDATION")) { + log.info("질문형 추천 실제 처리 - userId: {}", requestDto.getUserId()); + return generateAIResponseWithContext(requestDto, "질문형 추천"); + } + + // 사용자 질문 저장 + ChatConversation userQuestion = ChatConversation.builder() + .userId(requestDto.getUserId()) + .message(requestDto.getMessage()) + .sender(MessageSender.USER) + .createdAt(LocalDateTime.now()) + .build(); + chatConversationRepository.save(userQuestion); + + // 고정 로딩 메시지 + String loadingMessage = "당신에게 어울리는 칵테일은?\n 두구❤️두구💛두구💚두구💙두구💜두구🖤두구🤍두구🤎"; + + ChatConversation loadingBot = ChatConversation.builder() + .userId(requestDto.getUserId()) + .message(loadingMessage) + .sender(MessageSender.CHATBOT) + .createdAt(LocalDateTime.now()) + .build(); + ChatConversation savedLoading = chatConversationRepository.save(loadingBot); + + // 로딩 메시지 반환 (FE에서 이후 자동으로 실제 추천 요청) + return ChatResponseDto.builder() + .id(savedLoading.getId()) + .userId(requestDto.getUserId()) + .message(loadingMessage) + .sender(MessageSender.CHATBOT) + .type(MessageType.LOADING) + .createdAt(savedLoading.getCreatedAt()) + .metaData(ChatResponseDto.MetaData.builder() + .currentStep(0) + .actionType("LOADING_QA") + .isTyping(true) + .delay(2000) // 2초 후 자동 요청 + .build()) + .build(); + } } else if (currentStep >= 1 && currentStep <= 4) { // 단계별 추천 @@ -163,8 +211,6 @@ else if (currentStep >= 1 && currentStep <= 4) { // ========== 2순위: 키워드 감지 (하위 호환성) ========== if (isStepRecommendationTrigger(requestDto.getMessage())) { log.info("[LEGACY] 키워드 기반 단계별 추천 감지 - userId: {}", requestDto.getUserId()); - - // FE에서 currentStep을 보내지 않았을 때 자동 설정 requestDto.setCurrentStep(1); return handleStepRecommendation(requestDto); } @@ -563,15 +609,64 @@ private ChatResponseDto handleStepRecommendation(ChatRequestDto requestDto) { message = "좋아요! 이제 원하는 칵테일 스타일을 자유롭게 말씀해주세요 💬\n 없으면 'x', 또는 '없음' 을 입력해주세요!"; type = MessageType.INPUT; break; - case 4: + // Step 4에서 로딩 메시지 처리 + if (!"PROCESS_STEP_RECOMMENDATION".equals(requestDto.getMessage())) { + // 사용자 입력 저장 (Step 3의 답변) + if (requestDto.getMessage() != null && !requestDto.getMessage().trim().isEmpty()) { + ChatConversation userInput = ChatConversation.builder() + .userId(requestDto.getUserId()) + .message(requestDto.getMessage()) + .sender(MessageSender.USER) + .createdAt(LocalDateTime.now()) + .build(); + chatConversationRepository.save(userInput); + } + + // 로딩 메시지 생성 - userstyle 삭제 + String loadingMessage = "당신의 취향에 맞는 칵테일은?\n 두구❤️두구💛두구💚두구💙두구💜두구🖤두구🤍두구🤎"; + + ChatConversation loadingBot = ChatConversation.builder() + .userId(requestDto.getUserId()) + .message(loadingMessage) + .sender(MessageSender.CHATBOT) + .createdAt(LocalDateTime.now()) + .build(); + ChatConversation savedLoading = chatConversationRepository.save(loadingBot); + + // 로딩 메시지 반환 + return ChatResponseDto.builder() + .id(savedLoading.getId()) + .userId(requestDto.getUserId()) + .message(loadingMessage) + .sender(MessageSender.CHATBOT) + .type(MessageType.LOADING) + .createdAt(savedLoading.getCreatedAt()) + .metaData(ChatResponseDto.MetaData.builder() + .currentStep(4) + .totalSteps(4) + .actionType("LOADING_STEP") + .isTyping(true) + .delay(2000) // 2초 후 자동 요청 + .build()) + .stepData(new StepRecommendationResponseDto( + 4, + null, + null, + null, + false + )) + .build(); + } + + // 실제 추천 처리 stepData = getFinalRecommendationsWithMessage( - parseAlcoholStrength(requestDto.getSelectedAlcoholStrength()), - parseAlcoholBaseType(requestDto.getSelectedAlcoholBaseType()), - requestDto.getMessage() + parseAlcoholStrength(requestDto.getSelectedAlcoholStrength()), + parseAlcoholBaseType(requestDto.getSelectedAlcoholBaseType()), + requestDto.getMessage() ); message = stepData.getStepTitle(); - type = MessageType.CARD_LIST; // 최종 추천은 카드 리스트 + type = MessageType.CARD_LIST; break; default: From cc52ac7d300fd6f13cf33d516e6185c91206ab78 Mon Sep 17 00:00:00 2001 From: GerHerMo Date: Thu, 9 Oct 2025 23:26:18 +0900 Subject: [PATCH 3/4] feat: STEP & QA loading msg --- .../chatbot/service/ChatbotService.java | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/back/domain/chatbot/service/ChatbotService.java b/src/main/java/com/back/domain/chatbot/service/ChatbotService.java index 1d92cfa..ca6b4ae 100644 --- a/src/main/java/com/back/domain/chatbot/service/ChatbotService.java +++ b/src/main/java/com/back/domain/chatbot/service/ChatbotService.java @@ -562,7 +562,6 @@ private ChatResponseDto createErrorResponse(String errorMessage) { .createdAt(LocalDateTime.now()) .build(); } - private ChatResponseDto handleStepRecommendation(ChatRequestDto requestDto) { Integer currentStep = requestDto.getCurrentStep(); @@ -581,6 +580,7 @@ private ChatResponseDto handleStepRecommendation(ChatRequestDto requestDto) { if (currentStep == null || currentStep <= 0) { currentStep = 1; } + StepRecommendationResponseDto stepData; String message; MessageType type; @@ -600,15 +600,16 @@ private ChatResponseDto handleStepRecommendation(ChatRequestDto requestDto) { case 3: stepData = new StepRecommendationResponseDto( - 3, - null, - null, - null, - false + 3, + null, + null, + null, + false ); - message = "좋아요! 이제 원하는 칵테일 스타일을 자유롭게 말씀해주세요 💬\n 없으면 'x', 또는 '없음' 을 입력해주세요!"; + message = "좋아요! 이제 원하는 칵테일 스타일을 자유롭게 말씀해주세요 💬\n없으면 'x', 또는 '없음'을 입력해주세요!"; type = MessageType.INPUT; break; + case 4: // Step 4에서 로딩 메시지 처리 if (!"PROCESS_STEP_RECOMMENDATION".equals(requestDto.getMessage())) { @@ -623,8 +624,8 @@ private ChatResponseDto handleStepRecommendation(ChatRequestDto requestDto) { chatConversationRepository.save(userInput); } - // 로딩 메시지 생성 - userstyle 삭제 - String loadingMessage = "당신의 취향에 맞는 칵테일은?\n 두구❤️두구💛두구💚두구💙두구💜두구🖤두구🤍두구🤎"; + // 고정 로딩 메시지 + String loadingMessage = "당신에게 어울리는 칵테일은?\n 두구❤️두구💛두구💚두구💙두구💜두구🖤두구🤍두구🤎"; ChatConversation loadingBot = ChatConversation.builder() .userId(requestDto.getUserId()) @@ -674,7 +675,8 @@ private ChatResponseDto handleStepRecommendation(ChatRequestDto requestDto) { message = "단계별 맞춤 취향 추천을 시작합니다! 🎯"; type = MessageType.RADIO_OPTIONS; } - // 봇 응답 저장 (사용자 메시지는 이미 위에서 저장) + + // 봇 응답 저장 ChatConversation botResponse = ChatConversation.builder() .userId(requestDto.getUserId()) .message(message) @@ -687,7 +689,7 @@ private ChatResponseDto handleStepRecommendation(ChatRequestDto requestDto) { ChatResponseDto.MetaData metaData = ChatResponseDto.MetaData.builder() .currentStep(currentStep) .totalSteps(4) - .isTyping(true) + .isTyping(type != MessageType.CARD_LIST) // 카드리스트는 타이핑 애니메이션 불필요 .delay(300) .build(); @@ -702,7 +704,6 @@ private ChatResponseDto handleStepRecommendation(ChatRequestDto requestDto) { .createdAt(savedResponse.getCreatedAt()) .build(); } - // ============ 단계별 추천 관련 메서드들 ============ // "ALL" 또는 null/빈값은 null로 처리하여 전체 선택 의미 From 5158628f7543a5068da128612dd7e063681c7b12 Mon Sep 17 00:00:00 2001 From: GerHerMo Date: Thu, 9 Oct 2025 23:55:54 +0900 Subject: [PATCH 4/4] fix: userStyleInput --- .../com/back/domain/chatbot/dto/ChatRequestDto.java | 3 +++ .../back/domain/chatbot/service/ChatbotService.java | 11 ++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/back/domain/chatbot/dto/ChatRequestDto.java b/src/main/java/com/back/domain/chatbot/dto/ChatRequestDto.java index 83f26e1..6b265c7 100644 --- a/src/main/java/com/back/domain/chatbot/dto/ChatRequestDto.java +++ b/src/main/java/com/back/domain/chatbot/dto/ChatRequestDto.java @@ -27,4 +27,7 @@ public class ChatRequestDto { private String selectedAlcoholStrength; private String selectedAlcoholBaseType; // selectedCocktailType 삭제 + + // Step 3에서 사용자가 입력한 칵테일 스타일 (검색 키워드로 사용) + private String userStyleInput; } \ No newline at end of file diff --git a/src/main/java/com/back/domain/chatbot/service/ChatbotService.java b/src/main/java/com/back/domain/chatbot/service/ChatbotService.java index ca6b4ae..13990f5 100644 --- a/src/main/java/com/back/domain/chatbot/service/ChatbotService.java +++ b/src/main/java/com/back/domain/chatbot/service/ChatbotService.java @@ -613,8 +613,9 @@ private ChatResponseDto handleStepRecommendation(ChatRequestDto requestDto) { case 4: // Step 4에서 로딩 메시지 처리 if (!"PROCESS_STEP_RECOMMENDATION".equals(requestDto.getMessage())) { - // 사용자 입력 저장 (Step 3의 답변) + // 사용자 입력 저장 (Step 3의 답변) 및 userStyleInput에 저장 if (requestDto.getMessage() != null && !requestDto.getMessage().trim().isEmpty()) { + // DB에 저장 ChatConversation userInput = ChatConversation.builder() .userId(requestDto.getUserId()) .message(requestDto.getMessage()) @@ -622,6 +623,10 @@ private ChatResponseDto handleStepRecommendation(ChatRequestDto requestDto) { .createdAt(LocalDateTime.now()) .build(); chatConversationRepository.save(userInput); + + // userStyleInput에 저장 (다음 요청에서 사용) + requestDto.setUserStyleInput(requestDto.getMessage()); + log.info("Step 3 사용자 입력 저장: {}", requestDto.getMessage()); } // 고정 로딩 메시지 @@ -660,11 +665,11 @@ private ChatResponseDto handleStepRecommendation(ChatRequestDto requestDto) { .build(); } - // 실제 추천 처리 + // 실제 추천 처리 - userStyleInput 사용 (PROCESS_STEP_RECOMMENDATION 키워드 아님) stepData = getFinalRecommendationsWithMessage( parseAlcoholStrength(requestDto.getSelectedAlcoholStrength()), parseAlcoholBaseType(requestDto.getSelectedAlcoholBaseType()), - requestDto.getMessage() + requestDto.getUserStyleInput() // message 대신 userStyleInput 사용 ); message = stepData.getStepTitle(); type = MessageType.CARD_LIST;