Skip to content

Commit c2ad5b9

Browse files
authored
[feat] 챗봇 질문형 추천 멘트 추가
[feat] 챗봇 질문형 추천 멘트 추가
2 parents 78b4720 + 5158628 commit c2ad5b9

File tree

2 files changed

+179
-27
lines changed

2 files changed

+179
-27
lines changed

src/main/java/com/back/domain/chatbot/dto/ChatRequestDto.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,7 @@ public class ChatRequestDto {
2727
private String selectedAlcoholStrength;
2828
private String selectedAlcoholBaseType;
2929
// selectedCocktailType 삭제
30+
31+
// Step 3에서 사용자가 입력한 칵테일 스타일 (검색 키워드로 사용)
32+
private String userStyleInput;
3033
}

src/main/java/com/back/domain/chatbot/service/ChatbotService.java

Lines changed: 176 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,14 @@ public class ChatbotService {
6262
private String responseRules;
6363
private ChatClient chatClient;
6464

65+
// 로딩 메시지 상수
66+
private static final String RECOMMENDATION_LOADING_MESSAGE =
67+
"당신에게 어울리는 칵테일은? 두구❤️두구💛두구💚두구💙두구💜두구🖤두구🤍두구🤎";
68+
69+
// 처리 완료 플래그 키워드
70+
private static final String PROCESS_STEP_RECOMMENDATION = "PROCESS_STEP_RECOMMENDATION";
71+
private static final String PROCESS_QA_RECOMMENDATION = "PROCESS_QA_RECOMMENDATION";
72+
6573
@PostConstruct
6674
public void init() throws IOException {
6775
this.systemPrompt = StreamUtils.copyToString(
@@ -97,9 +105,95 @@ public ChatResponseDto sendMessage(ChatRequestDto requestDto) {
97105
currentStep == 0 ? "QA" : "STEP");
98106

99107
if (currentStep == 0) {
100-
// 질문형 추천 (일반 AI 대화)
101-
log.info("질문형 추천 모드 진입 - userId: {}", requestDto.getUserId());
102-
return generateAIResponseWithContext(requestDto, "질문형 추천");
108+
// 질문형 추천 선택 시 안내 메시지와 INPUT 타입 반환
109+
if ("QA".equalsIgnoreCase(requestDto.getMessage()) ||
110+
requestDto.getMessage().contains("질문형")) {
111+
112+
log.info("질문형 추천 시작 - userId: {}", requestDto.getUserId());
113+
114+
// 사용자 선택 메시지 저장
115+
ChatConversation userChoice = ChatConversation.builder()
116+
.userId(requestDto.getUserId())
117+
.message("질문형 취향 찾기")
118+
.sender(MessageSender.USER)
119+
.createdAt(LocalDateTime.now())
120+
.build();
121+
chatConversationRepository.save(userChoice);
122+
123+
String guideMessage = "칵테일에 관련된 질문을 입력해주세요!";
124+
/*
125+
String guideMessage = "좋아요! 질문형 추천을 시작할게요 🎯\n" +
126+
"칵테일에 관련된 질문을 자유롭게 입력해주세요!\n" +
127+
"예시: 달콤한 칵테일 추천해줘, 파티용 칵테일이 필요해, 초보자용 칵테일 알려줘";
128+
*/
129+
130+
ChatConversation botGuide = ChatConversation.builder()
131+
.userId(requestDto.getUserId())
132+
.message(guideMessage)
133+
.sender(MessageSender.CHATBOT)
134+
.createdAt(LocalDateTime.now())
135+
.build();
136+
ChatConversation savedGuide = chatConversationRepository.save(botGuide);
137+
138+
// INPUT 타입으로 반환하여 사용자 입력 유도
139+
return ChatResponseDto.builder()
140+
.id(savedGuide.getId())
141+
.userId(requestDto.getUserId())
142+
.message(guideMessage)
143+
.sender(MessageSender.CHATBOT)
144+
.type(MessageType.INPUT)
145+
.createdAt(savedGuide.getCreatedAt())
146+
.metaData(ChatResponseDto.MetaData.builder()
147+
.currentStep(0)
148+
.actionType("질문형 추천")
149+
.build())
150+
.build();
151+
}
152+
153+
// 실제 질문이 들어온 경우 - 먼저 로딩 메시지 반환
154+
if (requestDto.getMessage() != null && !requestDto.getMessage().trim().isEmpty()) {
155+
// 로딩 메시지인지 확인 (두구두구 메시지 이후의 실제 처리 요청)
156+
if (requestDto.getMessage().contains("PROCESS_RECOMMENDATION")) {
157+
log.info("질문형 추천 실제 처리 - userId: {}", requestDto.getUserId());
158+
return generateAIResponseWithContext(requestDto, "질문형 추천");
159+
}
160+
161+
// 사용자 질문 저장
162+
ChatConversation userQuestion = ChatConversation.builder()
163+
.userId(requestDto.getUserId())
164+
.message(requestDto.getMessage())
165+
.sender(MessageSender.USER)
166+
.createdAt(LocalDateTime.now())
167+
.build();
168+
chatConversationRepository.save(userQuestion);
169+
170+
// 고정 로딩 메시지
171+
String loadingMessage = "당신에게 어울리는 칵테일은?\n 두구❤️두구💛두구💚두구💙두구💜두구🖤두구🤍두구🤎";
172+
173+
ChatConversation loadingBot = ChatConversation.builder()
174+
.userId(requestDto.getUserId())
175+
.message(loadingMessage)
176+
.sender(MessageSender.CHATBOT)
177+
.createdAt(LocalDateTime.now())
178+
.build();
179+
ChatConversation savedLoading = chatConversationRepository.save(loadingBot);
180+
181+
// 로딩 메시지 반환 (FE에서 이후 자동으로 실제 추천 요청)
182+
return ChatResponseDto.builder()
183+
.id(savedLoading.getId())
184+
.userId(requestDto.getUserId())
185+
.message(loadingMessage)
186+
.sender(MessageSender.CHATBOT)
187+
.type(MessageType.LOADING)
188+
.createdAt(savedLoading.getCreatedAt())
189+
.metaData(ChatResponseDto.MetaData.builder()
190+
.currentStep(0)
191+
.actionType("LOADING_QA")
192+
.isTyping(true)
193+
.delay(2000) // 2초 후 자동 요청
194+
.build())
195+
.build();
196+
}
103197
}
104198
else if (currentStep >= 1 && currentStep <= 4) {
105199
// 단계별 추천
@@ -117,8 +211,6 @@ else if (currentStep >= 1 && currentStep <= 4) {
117211
// ========== 2순위: 키워드 감지 (하위 호환성) ==========
118212
if (isStepRecommendationTrigger(requestDto.getMessage())) {
119213
log.info("[LEGACY] 키워드 기반 단계별 추천 감지 - userId: {}", requestDto.getUserId());
120-
121-
// FE에서 currentStep을 보내지 않았을 때 자동 설정
122214
requestDto.setCurrentStep(1);
123215
return handleStepRecommendation(requestDto);
124216
}
@@ -470,9 +562,21 @@ private ChatResponseDto createErrorResponse(String errorMessage) {
470562
.createdAt(LocalDateTime.now())
471563
.build();
472564
}
473-
474565
private ChatResponseDto handleStepRecommendation(ChatRequestDto requestDto) {
475566
Integer currentStep = requestDto.getCurrentStep();
567+
568+
// 단계별 추천 선택 시 처리
569+
if (currentStep == 1 && "STEP".equalsIgnoreCase(requestDto.getMessage())) {
570+
// 사용자 선택 메시지 저장
571+
ChatConversation userChoice = ChatConversation.builder()
572+
.userId(requestDto.getUserId())
573+
.message("단계별 취향 찾기")
574+
.sender(MessageSender.USER)
575+
.createdAt(LocalDateTime.now())
576+
.build();
577+
chatConversationRepository.save(userChoice);
578+
}
579+
476580
if (currentStep == null || currentStep <= 0) {
477581
currentStep = 1;
478582
}
@@ -496,24 +600,79 @@ private ChatResponseDto handleStepRecommendation(ChatRequestDto requestDto) {
496600

497601
case 3:
498602
stepData = new StepRecommendationResponseDto(
499-
3,
500-
null,
501-
null,
502-
null,
503-
false
603+
3,
604+
null,
605+
null,
606+
null,
607+
false
504608
);
505-
message = "좋아요! 이제 원하는 칵테일 스타일을 자유롭게 말씀해주세요 💬\n 없으면 'x', 또는 '없음' 을 입력해주세요!";
609+
message = "좋아요! 이제 원하는 칵테일 스타일을 자유롭게 말씀해주세요 💬\n없으면 'x', 또는 '없음'을 입력해주세요!";
506610
type = MessageType.INPUT;
507611
break;
508612

509613
case 4:
614+
// Step 4에서 로딩 메시지 처리
615+
if (!"PROCESS_STEP_RECOMMENDATION".equals(requestDto.getMessage())) {
616+
// 사용자 입력 저장 (Step 3의 답변) 및 userStyleInput에 저장
617+
if (requestDto.getMessage() != null && !requestDto.getMessage().trim().isEmpty()) {
618+
// DB에 저장
619+
ChatConversation userInput = ChatConversation.builder()
620+
.userId(requestDto.getUserId())
621+
.message(requestDto.getMessage())
622+
.sender(MessageSender.USER)
623+
.createdAt(LocalDateTime.now())
624+
.build();
625+
chatConversationRepository.save(userInput);
626+
627+
// userStyleInput에 저장 (다음 요청에서 사용)
628+
requestDto.setUserStyleInput(requestDto.getMessage());
629+
log.info("Step 3 사용자 입력 저장: {}", requestDto.getMessage());
630+
}
631+
632+
// 고정 로딩 메시지
633+
String loadingMessage = "당신에게 어울리는 칵테일은?\n 두구❤️두구💛두구💚두구💙두구💜두구🖤두구🤍두구🤎";
634+
635+
ChatConversation loadingBot = ChatConversation.builder()
636+
.userId(requestDto.getUserId())
637+
.message(loadingMessage)
638+
.sender(MessageSender.CHATBOT)
639+
.createdAt(LocalDateTime.now())
640+
.build();
641+
ChatConversation savedLoading = chatConversationRepository.save(loadingBot);
642+
643+
// 로딩 메시지 반환
644+
return ChatResponseDto.builder()
645+
.id(savedLoading.getId())
646+
.userId(requestDto.getUserId())
647+
.message(loadingMessage)
648+
.sender(MessageSender.CHATBOT)
649+
.type(MessageType.LOADING)
650+
.createdAt(savedLoading.getCreatedAt())
651+
.metaData(ChatResponseDto.MetaData.builder()
652+
.currentStep(4)
653+
.totalSteps(4)
654+
.actionType("LOADING_STEP")
655+
.isTyping(true)
656+
.delay(2000) // 2초 후 자동 요청
657+
.build())
658+
.stepData(new StepRecommendationResponseDto(
659+
4,
660+
null,
661+
null,
662+
null,
663+
false
664+
))
665+
.build();
666+
}
667+
668+
// 실제 추천 처리 - userStyleInput 사용 (PROCESS_STEP_RECOMMENDATION 키워드 아님)
510669
stepData = getFinalRecommendationsWithMessage(
511-
parseAlcoholStrength(requestDto.getSelectedAlcoholStrength()),
512-
parseAlcoholBaseType(requestDto.getSelectedAlcoholBaseType()),
513-
requestDto.getMessage()
670+
parseAlcoholStrength(requestDto.getSelectedAlcoholStrength()),
671+
parseAlcoholBaseType(requestDto.getSelectedAlcoholBaseType()),
672+
requestDto.getUserStyleInput() // message 대신 userStyleInput 사용
514673
);
515674
message = stepData.getStepTitle();
516-
type = MessageType.CARD_LIST; // 최종 추천은 카드 리스트
675+
type = MessageType.CARD_LIST;
517676
break;
518677

519678
default:
@@ -522,15 +681,6 @@ private ChatResponseDto handleStepRecommendation(ChatRequestDto requestDto) {
522681
type = MessageType.RADIO_OPTIONS;
523682
}
524683

525-
// 사용자 메시지 저장 (단계별 추천 요청)
526-
ChatConversation userMessage = ChatConversation.builder()
527-
.userId(requestDto.getUserId())
528-
.message(requestDto.getMessage())
529-
.sender(MessageSender.USER)
530-
.createdAt(LocalDateTime.now())
531-
.build();
532-
chatConversationRepository.save(userMessage);
533-
534684
// 봇 응답 저장
535685
ChatConversation botResponse = ChatConversation.builder()
536686
.userId(requestDto.getUserId())
@@ -544,7 +694,7 @@ private ChatResponseDto handleStepRecommendation(ChatRequestDto requestDto) {
544694
ChatResponseDto.MetaData metaData = ChatResponseDto.MetaData.builder()
545695
.currentStep(currentStep)
546696
.totalSteps(4)
547-
.isTyping(true)
697+
.isTyping(type != MessageType.CARD_LIST) // 카드리스트는 타이핑 애니메이션 불필요
548698
.delay(300)
549699
.build();
550700

@@ -559,7 +709,6 @@ private ChatResponseDto handleStepRecommendation(ChatRequestDto requestDto) {
559709
.createdAt(savedResponse.getCreatedAt())
560710
.build();
561711
}
562-
563712
// ============ 단계별 추천 관련 메서드들 ============
564713
// "ALL" 또는 null/빈값은 null로 처리하여 전체 선택 의미
565714

0 commit comments

Comments
 (0)