1313import com .back .domain .cocktail .enums .AlcoholBaseType ;
1414import com .back .domain .cocktail .enums .AlcoholStrength ;
1515import com .back .domain .cocktail .repository .CocktailRepository ;
16+ import com .fasterxml .jackson .databind .ObjectMapper ;
1617import jakarta .annotation .PostConstruct ;
1718import lombok .RequiredArgsConstructor ;
1819import lombok .extern .slf4j .Slf4j ;
2627import org .springframework .stereotype .Service ;
2728import org .springframework .transaction .annotation .Transactional ;
2829import org .springframework .util .StreamUtils ;
30+ import com .fasterxml .jackson .core .JsonProcessingException ;
2931
3032import java .io .IOException ;
3133import java .nio .charset .StandardCharsets ;
3234import java .time .LocalDateTime ;
3335import java .util .ArrayList ;
3436import java .util .List ;
37+ import java .util .Map ;
3538import java .util .stream .Collectors ;
3639
3740@ Service
@@ -43,6 +46,8 @@ public class ChatbotService {
4346 private final ChatConversationRepository chatConversationRepository ;
4447 private final CocktailRepository cocktailRepository ;
4548
49+ private final ObjectMapper objectMapper = new ObjectMapper (); // JSON 변환용
50+
4651 @ Value ("classpath:prompts/chatbot-system-prompt.txt" )
4752 private Resource systemPromptResource ;
4853
@@ -87,6 +92,8 @@ public void init() throws IOException {
8792
8893 @ Transactional
8994 public ChatResponseDto sendMessage (ChatRequestDto requestDto ) {
95+ saveUserMessage (requestDto );
96+
9097 try {
9198 Integer currentStep = requestDto .getCurrentStep ();
9299
@@ -185,8 +192,47 @@ else if (currentStep >= 1 && currentStep <= 4) {
185192 }
186193 }
187194
188- // ============ 수정된 메서드들 ============
195+ private void saveUserMessage (ChatRequestDto requestDto ) {
196+ String metadata = null ;
197+ if (requestDto .getSelectedValue () != null ) {
198+ try {
199+ // 사용자가 선택한 실제 값(value)을 JSON으로 저장
200+ metadata = objectMapper .writeValueAsString (Map .of ("selectedValue" , requestDto .getSelectedValue ()));
201+ } catch (JsonProcessingException e ) {
202+ log .error ("사용자 선택 값 JSON 직렬화 실패" , e );
203+ }
204+ }
205+
206+ ChatConversation userMessage = ChatConversation .builder ()
207+ .userId (requestDto .getUserId ())
208+ .message (requestDto .getMessage ()) // 사용자가 본 텍스트(label)
209+ .sender (MessageSender .USER )
210+ .createdAt (LocalDateTime .now ())
211+ .metadata (metadata ) // 선택한 실제 값(value)
212+ .build ();
213+ chatConversationRepository .save (userMessage );
214+ }
189215
216+ private ChatConversation saveBotResponse (Long userId , String message , Object stepData ) {
217+ String metadata = null ;
218+ if (stepData != null ) {
219+ try {
220+ // 봇이 보낸 옵션, 카드 등 구조화된 데이터를 JSON으로 저장
221+ metadata = objectMapper .writeValueAsString (stepData );
222+ } catch (JsonProcessingException e ) {
223+ log .error ("봇 응답 메타데이터 JSON 직렬화 실패" , e );
224+ }
225+ }
226+
227+ ChatConversation botResponse = ChatConversation .builder ()
228+ .userId (userId )
229+ .message (message )
230+ .sender (MessageSender .CHATBOT )
231+ .createdAt (LocalDateTime .now ())
232+ .metadata (metadata )
233+ .build ();
234+ return chatConversationRepository .save (botResponse );
235+ }
190236 /**
191237 * 대화 컨텍스트 빌드 - 변경사항: sender로 구분하여 대화 재구성
192238 */
@@ -242,8 +288,44 @@ public ChatConversation saveConversation(ChatRequestDto requestDto, String respo
242288 * 사용자 채팅 기록 조회 - 변경사항: sender 구분 없이 모든 메시지 시간순으로 조회
243289 */
244290 @ Transactional (readOnly = true )
245- public List <ChatConversation > getUserChatHistory (Long userId ) {
246- return chatConversationRepository .findByUserIdOrderByCreatedAtDesc (userId );
291+ public List <ChatResponseDto > getUserChatHistory (Long userId ) {
292+ List <ChatConversation > history = chatConversationRepository .findByUserIdOrderByCreatedAtAsc (userId ); // 시간순으로 변경
293+
294+ return history .stream ().map (conversation -> {
295+ ChatResponseDto .ChatResponseDtoBuilder builder = ChatResponseDto .builder ()
296+ .id (conversation .getId ())
297+ .userId (conversation .getUserId ())
298+ .message (conversation .getMessage ())
299+ .sender (conversation .getSender ())
300+ .createdAt (conversation .getCreatedAt ());
301+
302+ String metadata = conversation .getMetadata ();
303+ if (metadata != null && !metadata .isEmpty ()) {
304+ try {
305+ if (conversation .getSender () == MessageSender .CHATBOT ) {
306+ StepRecommendationResponseDto stepData = objectMapper .readValue (metadata , StepRecommendationResponseDto .class );
307+ builder .stepData (stepData );
308+
309+ if (stepData .getOptions () != null && !stepData .getOptions ().isEmpty ()) {
310+ builder .type (MessageType .RADIO_OPTIONS );
311+ } else if (stepData .getRecommendations () != null && !stepData .getRecommendations ().isEmpty ()) {
312+ builder .type (MessageType .CARD_LIST );
313+ } else {
314+ builder .type (MessageType .TEXT );
315+ }
316+ } else { // sender == USER
317+ // 사용자 메시지의 메타데이터는 FE에서 선택 처리 등에 활용 가능
318+ builder .type (MessageType .TEXT );
319+ }
320+ } catch (JsonProcessingException e ) {
321+ log .error ("대화 기록 metadata 역직렬화 실패 [ID: {}]: {}" , conversation .getId (), e .getMessage ());
322+ builder .type (MessageType .TEXT );
323+ }
324+ } else {
325+ builder .type (MessageType .TEXT );
326+ }
327+ return builder .build ();
328+ }).collect (Collectors .toList ());
247329 }
248330
249331 /**
@@ -429,8 +511,8 @@ private ChatConversation generateAIResponse(ChatRequestDto requestDto) {
429511 // 응답 후처리
430512 response = postProcessResponse (response , messageType );
431513
432- // 대화 저장 - 사용자 메시지와 봇 응답을 각각 저장하고 저장된 봇 응답 반환
433- return saveConversation (requestDto , response );
514+ // 봇 응답만 저장 -> 사용자 메시지는 sendmessage()에서 이미 저장됨
515+ return saveBotResponse (requestDto . getUserId () , response , null );
434516 }
435517
436518 /**
@@ -584,7 +666,7 @@ private ChatResponseDto handleStepRecommendation(ChatRequestDto requestDto) {
584666 .sender (MessageSender .CHATBOT )
585667 .createdAt (LocalDateTime .now ())
586668 .build ();
587- ChatConversation savedResponse = chatConversationRepository . save ( botResponse );
669+ ChatConversation savedResponse = saveBotResponse ( requestDto . getUserId (), message , stepData );
588670
589671 // 메타데이터 포함
590672 ChatResponseDto .MetaData metaData = ChatResponseDto .MetaData .builder ()
0 commit comments