From e2bdf351d3e933cfbbd31ca12053f6a197b1447e Mon Sep 17 00:00:00 2001 From: GerHerMo Date: Tue, 30 Sep 2025 16:32:07 +0900 Subject: [PATCH 1/9] feat: enum - MessageType --- .../domain/chatbot/enums/MessageType.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/main/java/com/back/domain/chatbot/enums/MessageType.java diff --git a/src/main/java/com/back/domain/chatbot/enums/MessageType.java b/src/main/java/com/back/domain/chatbot/enums/MessageType.java new file mode 100644 index 0000000..11e71ac --- /dev/null +++ b/src/main/java/com/back/domain/chatbot/enums/MessageType.java @@ -0,0 +1,19 @@ +package com.back.domain.chatbot.enums; + +public enum MessageType { + TEXT("텍스트"), // 일반 텍스트 메시지 + RADIO_OPTIONS("라디오옵션"), // 라디오 버튼 선택지 + CARD_LIST("카드리스트"), // 칵테일 추천 카드 리스트 + LOADING("로딩중"), // 로딩 메시지 + ERROR("에러"); // 에러 메시지 + + private final String description; + + MessageType(String description) { + this.description = description; + } + + public String getDescription() { + return description; + } +} \ No newline at end of file From b6e7b1d62ef11ae84639b3a0d84c7154f0899d4e Mon Sep 17 00:00:00 2001 From: GerHerMo Date: Tue, 30 Sep 2025 17:39:34 +0900 Subject: [PATCH 2/9] feat: ChatResponseDto update --- .../domain/chatbot/dto/ChatResponseDto.java | 52 ++++++++++++++++--- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/back/domain/chatbot/dto/ChatResponseDto.java b/src/main/java/com/back/domain/chatbot/dto/ChatResponseDto.java index e96d146..59170e8 100644 --- a/src/main/java/com/back/domain/chatbot/dto/ChatResponseDto.java +++ b/src/main/java/com/back/domain/chatbot/dto/ChatResponseDto.java @@ -1,6 +1,8 @@ package com.back.domain.chatbot.dto; +import com.back.domain.chatbot.enums.MessageType; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -11,22 +13,56 @@ @Setter @NoArgsConstructor @AllArgsConstructor +@Builder public class ChatResponseDto { - private String response; + private String message; // 텍스트 메시지 + private MessageType type; // 메시지 표시 타입 private LocalDateTime timestamp; - // 단계별 추천 관련 필드 (선택사항) - private StepRecommendationResponseDto stepRecommendation; + // 단계별 추천 관련 데이터 (type이 RADIO_OPTIONS 또는 CARD_LIST일 때 사용) + private StepRecommendationResponseDto stepData; - public ChatResponseDto(String response) { - this.response = response; + // 추가 메타데이터 + private MetaData metaData; + + // 생성자들 + public ChatResponseDto(String message) { + this.message = message; + this.type = MessageType.TEXT; this.timestamp = LocalDateTime.now(); } - public ChatResponseDto(String response, StepRecommendationResponseDto stepRecommendation) { - this.response = response; + public ChatResponseDto(String message, StepRecommendationResponseDto stepData) { + this.message = message; this.timestamp = LocalDateTime.now(); - this.stepRecommendation = stepRecommendation; + this.stepData = stepData; + + // stepData 내용에 따라 type 자동 설정 + if (stepData != null) { + if (stepData.getOptions() != null && !stepData.getOptions().isEmpty()) { + this.type = MessageType.RADIO_OPTIONS; + } else if (stepData.getRecommendations() != null && !stepData.getRecommendations().isEmpty()) { + this.type = MessageType.CARD_LIST; + } else { + this.type = MessageType.TEXT; + } + } else { + this.type = MessageType.TEXT; + } + } + + // 메타데이터 내부 클래스 + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class MetaData { + private Integer currentStep; // 현재 단계 (단계별 추천) + private Integer totalSteps; // 전체 단계 수 + private Boolean isTyping; // 타이핑 애니메이션 표시 여부 + private Integer delay; // 메시지 표시 지연 시간(ms) + private String actionType; // 버튼 액션 타입 } } \ No newline at end of file From 0fec9882a9c518dda1ed7574a135fc502d6035c9 Mon Sep 17 00:00:00 2001 From: GerHerMo Date: Tue, 30 Sep 2025 17:44:42 +0900 Subject: [PATCH 3/9] feat: Service update --- .../chatbot/service/ChatbotService.java | 54 +++++++++++++------ 1 file changed, 38 insertions(+), 16 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 6c6b55d..4cd33f6 100644 --- a/src/main/java/com/back/domain/chatbot/service/ChatbotService.java +++ b/src/main/java/com/back/domain/chatbot/service/ChatbotService.java @@ -6,6 +6,7 @@ import com.back.domain.chatbot.dto.StepRecommendationResponseDto; import com.back.domain.chatbot.entity.ChatConversation; import com.back.domain.chatbot.enums.MessageSender; +import com.back.domain.chatbot.enums.MessageType; import com.back.domain.chatbot.repository.ChatConversationRepository; import com.back.domain.cocktail.dto.CocktailSummaryResponseDto; import com.back.domain.cocktail.entity.Cocktail; @@ -98,8 +99,8 @@ public ChatResponseDto sendMessage(ChatRequestDto requestDto) { log.info("Normal chat mode for userId: {}", requestDto.getUserId()); - // 메시지 타입 감지 - MessageType messageType = detectMessageType(requestDto.getMessage()); + // 메시지 타입 감지 (내부 enum 사용) + InternalMessageType messageType = detectMessageType(requestDto.getMessage()); // 최근 대화 기록 조회 (최신 10개 메시지 - USER와 CHATBOT 메시지 모두 포함) List recentChats = @@ -125,7 +126,12 @@ public ChatResponseDto sendMessage(ChatRequestDto requestDto) { // 대화 저장 - 사용자 메시지와 봇 응답을 각각 저장 saveConversation(requestDto, response); - return new ChatResponseDto(response); + // 새로운 구조로 ChatResponseDto 생성 + return ChatResponseDto.builder() + .message(response) + .type(MessageType.TEXT) + .timestamp(LocalDateTime.now()) + .build(); } catch (Exception e) { log.error("채팅 응답 생성 중 오류 발생: ", e); @@ -240,7 +246,7 @@ public boolean isFirstConversation(Long userId) { // ============ 기존 메서드들 (변경 없음) ============ - private String buildSystemMessage(MessageType type) { + private String buildSystemMessage(InternalMessageType type) { StringBuilder sb = new StringBuilder(systemPrompt); // 메시지 타입별 추가 지시사항 @@ -261,11 +267,11 @@ private String buildSystemMessage(MessageType type) { return sb.toString(); } - private String buildUserMessage(String userMessage, MessageType type) { + private String buildUserMessage(String userMessage, InternalMessageType type) { return userMessage + "\n\n" + responseRules; } - private OpenAiChatOptions getOptionsForMessageType(MessageType type) { + private OpenAiChatOptions getOptionsForMessageType(InternalMessageType type) { return switch (type) { case RECIPE -> OpenAiChatOptions.builder() .withTemperature(0.3) // 정확성 중시 @@ -286,14 +292,14 @@ private OpenAiChatOptions getOptionsForMessageType(MessageType type) { }; } - private String postProcessResponse(String response, MessageType type) { + private String postProcessResponse(String response, InternalMessageType type) { // 응답 길이 제한 확인 if (response.length() > 500) { response = response.substring(0, 497) + "..."; } // 이모지 추가 (타입별) - if (type == MessageType.RECIPE && !response.contains("🍹")) { + if (type == InternalMessageType.RECIPE && !response.contains("🍹")) { response = "🍹 " + response; } @@ -309,28 +315,32 @@ private ChatResponseDto handleError(Exception e) { errorMessage = "응답 시간이 초과되었습니다. 다시 시도해주세요."; } - return new ChatResponseDto(errorMessage); + return ChatResponseDto.builder() + .message(errorMessage) + .type(MessageType.ERROR) + .timestamp(LocalDateTime.now()) + .build(); } - public enum MessageType { + public enum InternalMessageType { RECIPE, RECOMMENDATION, QUESTION, CASUAL_CHAT } - private MessageType detectMessageType(String message) { + private InternalMessageType detectMessageType(String message) { String lower = message.toLowerCase(); if (lower.contains("레시피") || lower.contains("만드는") || lower.contains("제조") || lower.contains("recipe")) { - return MessageType.RECIPE; + return InternalMessageType.RECIPE; } else if (lower.contains("추천") || lower.contains("어때") || lower.contains("뭐가 좋") || lower.contains("recommend")) { - return MessageType.RECOMMENDATION; + return InternalMessageType.RECOMMENDATION; } else if (lower.contains("?") || lower.contains("뭐") || lower.contains("어떻") || lower.contains("왜")) { - return MessageType.QUESTION; + return InternalMessageType.QUESTION; } - return MessageType.CASUAL_CHAT; + return InternalMessageType.CASUAL_CHAT; } // 단계별 추천 시작 키워드 감지 @@ -382,7 +392,19 @@ private ChatResponseDto handleStepRecommendation(ChatRequestDto requestDto) { // 대화 기록 저장 - 변경된 방식으로 저장 saveConversation(requestDto, chatResponse); - return new ChatResponseDto(chatResponse, stepRecommendation); + // 메타데이터 생성 (단계별 추천용) + ChatResponseDto.MetaData metaData = ChatResponseDto.MetaData.builder() + .currentStep(currentStep) + .totalSteps(4) + .isTyping(false) + .build(); + + return ChatResponseDto.builder() + .message(chatResponse) + .timestamp(LocalDateTime.now()) + .stepData(stepRecommendation) + .metaData(metaData) + .build(); } // ============ 단계별 추천 관련 메서드들 (변경 없음) ============ From 4e9206fd7e9782f87382e08c784e7618ceca1f86 Mon Sep 17 00:00:00 2001 From: GerHerMo Date: Tue, 30 Sep 2025 17:52:11 +0900 Subject: [PATCH 4/9] feat: Service update v2 with Opus4.1 --- .../chatbot/service/ChatbotService.java | 162 ++++++++++-------- 1 file changed, 91 insertions(+), 71 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 4cd33f6..e749294 100644 --- a/src/main/java/com/back/domain/chatbot/service/ChatbotService.java +++ b/src/main/java/com/back/domain/chatbot/service/ChatbotService.java @@ -89,44 +89,17 @@ public void init() throws IOException { @Transactional public ChatResponseDto sendMessage(ChatRequestDto requestDto) { try { - // 단계별 추천 모드 확인 (currentStep이 있으면 무조건 단계별 추천 모드) + // 단계별 추천 모드 if (requestDto.isStepRecommendation() || - requestDto.getCurrentStep() != null || - isStepRecommendationTrigger(requestDto.getMessage())) { - log.info("Recommendation chat mode for userId: {}", requestDto.getUserId()); + requestDto.getCurrentStep() != null) { + return handleStepRecommendation(requestDto); } - log.info("Normal chat mode for userId: {}", requestDto.getUserId()); - - // 메시지 타입 감지 (내부 enum 사용) - InternalMessageType messageType = detectMessageType(requestDto.getMessage()); - - // 최근 대화 기록 조회 (최신 10개 메시지 - USER와 CHATBOT 메시지 모두 포함) - List recentChats = - chatConversationRepository.findTop20ByUserIdOrderByCreatedAtDesc(requestDto.getUserId()); - - // 대화 컨텍스트 생성 - String conversationContext = buildConversationContext(recentChats); - - // ChatClient 빌더 생성 - .message 체인 방식 포기 - var promptBuilder = chatClient.prompt() - .system(buildSystemMessage(messageType) + conversationContext) - .user(buildUserMessage(requestDto.getMessage(), messageType)); + // 일반 대화 모드 + String response = generateAIResponse(requestDto); - // 응답 생성 - String response = promptBuilder - .options(getOptionsForMessageType(messageType)) - .call() - .content(); - - // 응답 후처리 - response = postProcessResponse(response, messageType); - - // 대화 저장 - 사용자 메시지와 봇 응답을 각각 저장 - saveConversation(requestDto, response); - - // 새로운 구조로 ChatResponseDto 생성 + // 일반 텍스트 응답 생성 (type이 자동으로 TEXT로 설정됨) return ChatResponseDto.builder() .message(response) .type(MessageType.TEXT) @@ -135,7 +108,13 @@ public ChatResponseDto sendMessage(ChatRequestDto requestDto) { } catch (Exception e) { log.error("채팅 응답 생성 중 오류 발생: ", e); - return handleError(e); + + // 에러 응답 + return ChatResponseDto.builder() + .message("죄송합니다. 일시적인 오류가 발생했습니다.") + .type(MessageType.ERROR) + .timestamp(LocalDateTime.now()) + .build(); } } @@ -306,19 +285,53 @@ private String postProcessResponse(String response, InternalMessageType type) { return response; } - private ChatResponseDto handleError(Exception e) { - String errorMessage = "죄송합니다. 잠시 후 다시 시도해주세요."; + /** + * AI 응답 생성 + */ + private String generateAIResponse(ChatRequestDto requestDto) { + log.info("Normal chat mode for userId: {}", requestDto.getUserId()); - if (e.getMessage().contains("rate limit")) { - errorMessage = "요청이 너무 많습니다. 잠시 후 다시 시도해주세요."; - } else if (e.getMessage().contains("timeout")) { - errorMessage = "응답 시간이 초과되었습니다. 다시 시도해주세요."; - } + // 메시지 타입 감지 (내부 enum 사용) + InternalMessageType messageType = detectMessageType(requestDto.getMessage()); + + // 최근 대화 기록 조회 (최신 20개 메시지 - USER와 CHATBOT 메시지 모두 포함) + List recentChats = + chatConversationRepository.findTop20ByUserIdOrderByCreatedAtDesc(requestDto.getUserId()); + + // 대화 컨텍스트 생성 + String conversationContext = buildConversationContext(recentChats); + + // ChatClient 빌더 생성 + var promptBuilder = chatClient.prompt() + .system(buildSystemMessage(messageType) + conversationContext) + .user(buildUserMessage(requestDto.getMessage(), messageType)); + // 응답 생성 + String response = promptBuilder + .options(getOptionsForMessageType(messageType)) + .call() + .content(); + + // 응답 후처리 + response = postProcessResponse(response, messageType); + + // 대화 저장 - 사용자 메시지와 봇 응답을 각각 저장 + saveConversation(requestDto, response); + + return response; + } + + /** + * 로딩 메시지 생성 + */ + public ChatResponseDto createLoadingMessage() { return ChatResponseDto.builder() - .message(errorMessage) - .type(MessageType.ERROR) + .message("응답을 생성하는 중...") + .type(MessageType.LOADING) .timestamp(LocalDateTime.now()) + .metaData(ChatResponseDto.MetaData.builder() + .isTyping(true) + .build()) .build(); } @@ -349,61 +362,68 @@ private boolean isStepRecommendationTrigger(String message) { return lower.contains("단계별 추천"); } - // 단계별 추천 처리 통합 메서드 - 변경사항: 대화 저장 방식 변경 private ChatResponseDto handleStepRecommendation(ChatRequestDto requestDto) { Integer currentStep = requestDto.getCurrentStep(); - - // 단계가 지정되지 않았거나 첫 시작인 경우 if (currentStep == null || currentStep <= 0) { currentStep = 1; } - StepRecommendationResponseDto stepRecommendation; - String chatResponse; + StepRecommendationResponseDto stepData; + String message; + MessageType type; switch (currentStep) { case 1: - stepRecommendation = getAlcoholStrengthOptions(); - chatResponse = "단계별로 취향을 찾아드릴게요! 🎯\n" + - "원하시는 도수를 선택해주세요! \n" + - "잘 모르는 항목은 '전체'로 체크하셔도 괜찮아요."; + stepData = getAlcoholStrengthOptions(); + message = "단계별 맞춤 추천을 시작합니다! 🎯\n원하시는 도수를 선택해주세요!"; + type = MessageType.RADIO_OPTIONS; break; + case 2: - stepRecommendation = getAlcoholBaseTypeOptions(requestDto.getSelectedAlcoholStrength()); - chatResponse = "좋은 선택이네요! 이제 베이스가 될 술을 선택해주세요 🍸"; + stepData = getAlcoholBaseTypeOptions(requestDto.getSelectedAlcoholStrength()); + message = "좋은 선택이네요! 이제 베이스가 될 술을 선택해주세요 🍸"; + type = MessageType.RADIO_OPTIONS; break; + case 3: - stepRecommendation = getCocktailTypeOptions(requestDto.getSelectedAlcoholStrength(), requestDto.getSelectedAlcoholBaseType()); - chatResponse = "완벽해요! 마지막으로 어떤 스타일로 즐기실 건가요? 🥃"; + stepData = getCocktailTypeOptions( + requestDto.getSelectedAlcoholStrength(), + requestDto.getSelectedAlcoholBaseType() + ); + message = "완벽해요! 마지막으로 어떤 스타일로 즐기실 건가요? 🥃"; + type = MessageType.RADIO_OPTIONS; break; + case 4: - stepRecommendation = getFinalRecommendations( - requestDto.getSelectedAlcoholStrength(), - requestDto.getSelectedAlcoholBaseType(), - requestDto.getSelectedCocktailType() + stepData = getFinalRecommendations( + requestDto.getSelectedAlcoholStrength(), + requestDto.getSelectedAlcoholBaseType(), + requestDto.getSelectedCocktailType() ); - chatResponse = stepRecommendation.getStepTitle(); + message = stepData.getStepTitle(); + type = MessageType.CARD_LIST; // 최종 추천은 카드 리스트 break; + default: - stepRecommendation = getAlcoholStrengthOptions(); - chatResponse = "단계별 맞춤 추천을 시작합니다! 🎯"; + stepData = getAlcoholStrengthOptions(); + message = "단계별 맞춤 추천을 시작합니다! 🎯"; + type = MessageType.RADIO_OPTIONS; } - // 대화 기록 저장 - 변경된 방식으로 저장 - saveConversation(requestDto, chatResponse); - - // 메타데이터 생성 (단계별 추천용) + // 메타데이터 포함 ChatResponseDto.MetaData metaData = ChatResponseDto.MetaData.builder() .currentStep(currentStep) .totalSteps(4) - .isTyping(false) + .isTyping(true) + .delay(300) .build(); return ChatResponseDto.builder() - .message(chatResponse) - .timestamp(LocalDateTime.now()) - .stepData(stepRecommendation) + .message(message) + .type(type) + .stepData(stepData) .metaData(metaData) + .timestamp(LocalDateTime.now()) .build(); } From 952c379c3d592683d1890858de79f7a6cda5236e Mon Sep 17 00:00:00 2001 From: GerHerMo Date: Wed, 1 Oct 2025 10:09:08 +0900 Subject: [PATCH 5/9] fix: isrecom~ add --- .../com/back/domain/chatbot/service/ChatbotService.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 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 e749294..de7d63d 100644 --- a/src/main/java/com/back/domain/chatbot/service/ChatbotService.java +++ b/src/main/java/com/back/domain/chatbot/service/ChatbotService.java @@ -89,10 +89,11 @@ public void init() throws IOException { @Transactional public ChatResponseDto sendMessage(ChatRequestDto requestDto) { try { - // 단계별 추천 모드 + // 단계별 추천 모드 확인 (currentStep이 있으면 무조건 단계별 추천 모드) if (requestDto.isStepRecommendation() || - requestDto.getCurrentStep() != null) { - + requestDto.getCurrentStep() != null || + isStepRecommendationTrigger(requestDto.getMessage())) { + log.info("Recommendation chat mode for userId: {}", requestDto.getUserId()); return handleStepRecommendation(requestDto); } From 4d92a086150a5aa7e2cde57d0911d1d1e964b95c Mon Sep 17 00:00:00 2001 From: GerHerMo Date: Wed, 1 Oct 2025 11:43:55 +0900 Subject: [PATCH 6/9] feat: greeting - change Dto --- .../chatbot/controller/ChatbotController.java | 4 +-- .../chatbot/service/ChatbotService.java | 36 +++++++++++++++++-- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/back/domain/chatbot/controller/ChatbotController.java b/src/main/java/com/back/domain/chatbot/controller/ChatbotController.java index 4d76112..e274d01 100644 --- a/src/main/java/com/back/domain/chatbot/controller/ChatbotController.java +++ b/src/main/java/com/back/domain/chatbot/controller/ChatbotController.java @@ -64,9 +64,9 @@ public ResponseEntity> saveBotMessage(@Valid @RequestBo @PostMapping("/greeting/{userId}") @Operation(summary = "인사말 생성", description = "사용자가 채팅을 시작할 때 기본 인사말을 생성하고 저장") - public ResponseEntity> createGreeting(@PathVariable Long userId) { + public ResponseEntity> createGreeting(@PathVariable Long userId) { try { - ChatConversation greeting = chatbotService.createGreetingMessage(userId); + ChatResponseDto greeting = chatbotService.createGreetingMessage(userId); return ResponseEntity.ok(RsData.successOf(greeting)); } catch (Exception e) { log.error("인사말 생성 중 오류 발생: ", e); 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 de7d63d..c45aa5c 100644 --- a/src/main/java/com/back/domain/chatbot/service/ChatbotService.java +++ b/src/main/java/com/back/domain/chatbot/service/ChatbotService.java @@ -198,21 +198,53 @@ public ChatConversation saveBotMessage(SaveBotMessageDto dto) { /** * 기본 인사말 생성 및 저장 * 채팅 시작 시 호출하여 인사말을 DB에 저장 + * MessageType.BUTTON_OPTIONS와 options 데이터를 포함한 ChatResponseDto 반환 */ @Transactional - public ChatConversation createGreetingMessage(Long userId) { + public ChatResponseDto createGreetingMessage(Long userId) { String greetingMessage = "안녕하세요! 🍹 바텐더 '쑤리'에요.\n" + "취향에 맞는 칵테일을 추천해드릴게요!\n" + "어떤 유형으로 찾아드릴까요?"; + // 선택 옵션 생성 + List options = List.of( + new StepRecommendationResponseDto.StepOption( + "QA", + "질문형 취향 찾기", + null + ), + new StepRecommendationResponseDto.StepOption( + "STEP", + "단계별 취향 찾기", + null + ) + ); + + // StepRecommendationResponseDto 생성 + StepRecommendationResponseDto stepData = new StepRecommendationResponseDto( + 0, // 인사말은 step 0 + greetingMessage, + options, + null, + false + ); + + // DB에 인사말 저장 ChatConversation greeting = ChatConversation.builder() .userId(userId) .message(greetingMessage) .sender(MessageSender.CHATBOT) .createdAt(LocalDateTime.now()) .build(); + chatConversationRepository.save(greeting); - return chatConversationRepository.save(greeting); + // ChatResponseDto 반환 + return ChatResponseDto.builder() + .message(greetingMessage) + .type(MessageType.RADIO_OPTIONS) + .stepData(stepData) + .timestamp(LocalDateTime.now()) + .build(); } /** From 9205dc0996d30ca96e7114c8f4cfd523b82eb499 Mon Sep 17 00:00:00 2001 From: GerHerMo Date: Wed, 1 Oct 2025 12:27:47 +0900 Subject: [PATCH 7/9] fix: greetingExists check for don't save duplicate greeting --- .../ChatConversationRepository.java | 2 ++ .../chatbot/service/ChatbotService.java | 27 ++++++++++++------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/back/domain/chatbot/repository/ChatConversationRepository.java b/src/main/java/com/back/domain/chatbot/repository/ChatConversationRepository.java index d412cab..6e8dd10 100644 --- a/src/main/java/com/back/domain/chatbot/repository/ChatConversationRepository.java +++ b/src/main/java/com/back/domain/chatbot/repository/ChatConversationRepository.java @@ -12,4 +12,6 @@ public interface ChatConversationRepository extends JpaRepository findByUserIdOrderByCreatedAtDesc(Long userId); List findTop20ByUserIdOrderByCreatedAtDesc(Long userId); + + boolean existsByUserIdAndMessage(Long userId, String message); } \ 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 c45aa5c..d4a738d 100644 --- a/src/main/java/com/back/domain/chatbot/service/ChatbotService.java +++ b/src/main/java/com/back/domain/chatbot/service/ChatbotService.java @@ -198,7 +198,8 @@ public ChatConversation saveBotMessage(SaveBotMessageDto dto) { /** * 기본 인사말 생성 및 저장 * 채팅 시작 시 호출하여 인사말을 DB에 저장 - * MessageType.BUTTON_OPTIONS와 options 데이터를 포함한 ChatResponseDto 반환 + * 이미 동일한 인사말이 존재하면 중복 저장하지 않음 + * MessageType.RADIO_OPTIONS와 options 데이터를 포함한 ChatResponseDto 반환 */ @Transactional public ChatResponseDto createGreetingMessage(Long userId) { @@ -229,14 +230,22 @@ public ChatResponseDto createGreetingMessage(Long userId) { false ); - // DB에 인사말 저장 - ChatConversation greeting = ChatConversation.builder() - .userId(userId) - .message(greetingMessage) - .sender(MessageSender.CHATBOT) - .createdAt(LocalDateTime.now()) - .build(); - chatConversationRepository.save(greeting); + // 중복 확인: 동일한 인사말이 이미 존재하는지 확인 + boolean greetingExists = chatConversationRepository.existsByUserIdAndMessage(userId, greetingMessage); + + // 중복되지 않을 경우에만 DB에 저장 + if (!greetingExists) { + ChatConversation greeting = ChatConversation.builder() + .userId(userId) + .message(greetingMessage) + .sender(MessageSender.CHATBOT) + .createdAt(LocalDateTime.now()) + .build(); + chatConversationRepository.save(greeting); + log.info("인사말 저장 완료 - userId: {}", userId); + } else { + log.info("이미 인사말이 존재하여 저장 생략 - userId: {}", userId); + } // ChatResponseDto 반환 return ChatResponseDto.builder() From 5130493c9afd1a36e2cbc84ea9e868b4741043fb Mon Sep 17 00:00:00 2001 From: GerHerMo Date: Wed, 1 Oct 2025 12:45:06 +0900 Subject: [PATCH 8/9] feat: options.add - ALL --- .../chatbot/service/ChatbotService.java | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 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 d4a738d..cae2dec 100644 --- a/src/main/java/com/back/domain/chatbot/service/ChatbotService.java +++ b/src/main/java/com/back/domain/chatbot/service/ChatbotService.java @@ -469,11 +469,18 @@ private ChatResponseDto handleStepRecommendation(ChatRequestDto requestDto) { .build(); } - // ============ 단계별 추천 관련 메서드들 (변경 없음) ============ + // ============ 단계별 추천 관련 메서드들 ============ private StepRecommendationResponseDto getAlcoholStrengthOptions() { List options = new ArrayList<>(); + // "전체" 옵션 추가 + options.add(new StepRecommendationResponseDto.StepOption( + "ALL", + "전체", + null + )); + for (AlcoholStrength strength : AlcoholStrength.values()) { options.add(new StepRecommendationResponseDto.StepOption( strength.name(), @@ -494,6 +501,13 @@ private StepRecommendationResponseDto getAlcoholStrengthOptions() { private StepRecommendationResponseDto getAlcoholBaseTypeOptions(AlcoholStrength alcoholStrength) { List options = new ArrayList<>(); + // "전체" 옵션 추가 + options.add(new StepRecommendationResponseDto.StepOption( + "ALL", + "전체", + null + )); + for (AlcoholBaseType baseType : AlcoholBaseType.values()) { options.add(new StepRecommendationResponseDto.StepOption( baseType.name(), @@ -514,6 +528,13 @@ private StepRecommendationResponseDto getAlcoholBaseTypeOptions(AlcoholStrength private StepRecommendationResponseDto getCocktailTypeOptions(AlcoholStrength alcoholStrength, AlcoholBaseType alcoholBaseType) { List options = new ArrayList<>(); + // "전체" 옵션 추가 + options.add(new StepRecommendationResponseDto.StepOption( + "ALL", + "전체", + null + )); + for (CocktailType cocktailType : CocktailType.values()) { options.add(new StepRecommendationResponseDto.StepOption( cocktailType.name(), @@ -536,9 +557,10 @@ private StepRecommendationResponseDto getFinalRecommendations( AlcoholBaseType alcoholBaseType, CocktailType cocktailType) { // 필터링 조건에 맞는 칵테일 검색 - List strengths = List.of(alcoholStrength); - List baseTypes = List.of(alcoholBaseType); - List cocktailTypes = List.of(cocktailType); + // "ALL" 선택 시 해당 필터를 null로 처리하여 전체 검색 + List strengths = (alcoholStrength == null) ? null : List.of(alcoholStrength); + List baseTypes = (alcoholBaseType == null) ? null : List.of(alcoholBaseType); + List cocktailTypes = (cocktailType == null) ? null : List.of(cocktailType); Page cocktailPage = cocktailRepository.searchWithFilters( null, // 키워드 없음 From c6e4e576c50b8541edf1ea5495de7d2bf3735527 Mon Sep 17 00:00:00 2001 From: GerHerMo Date: Wed, 1 Oct 2025 12:47:15 +0900 Subject: [PATCH 9/9] fix: MaxPage=3 on StepRecommendation --- .../java/com/back/domain/chatbot/service/ChatbotService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 cae2dec..e8df5e1 100644 --- a/src/main/java/com/back/domain/chatbot/service/ChatbotService.java +++ b/src/main/java/com/back/domain/chatbot/service/ChatbotService.java @@ -567,7 +567,7 @@ private StepRecommendationResponseDto getFinalRecommendations( strengths, cocktailTypes, baseTypes, - PageRequest.of(0, 5) // 최대 5개 추천 + PageRequest.of(0, 3) // 최대 3개 추천 ); List recommendations = cocktailPage.getContent().stream()