Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ public ResponseEntity<RsData<ChatResponseDto>> sendMessage(@Valid @RequestBody C

@GetMapping("/history/user/{userId}")
@Operation(summary = "유저 대화 히스토리", description = "사용자 채팅 기록 조회")
public ResponseEntity<RsData<List<ChatConversation>>> getUserChatHistory(@PathVariable Long userId) {
public ResponseEntity<RsData<List<ChatResponseDto>>> getUserChatHistory(@PathVariable Long userId) {
try {
List<ChatConversation> history = chatbotService.getUserChatHistory(userId);
List<ChatResponseDto> history = chatbotService.getUserChatHistory(userId);
return ResponseEntity.ok(RsData.successOf(history));
} catch (Exception e) {
log.error("사용자 채팅 기록 조회 중 오류 발생: ", e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public class ChatRequestDto {

private Long userId;

private String selectedValue; // 예: "NON_ALCOHOLIC"
// 단계별 추천 관련 필드들
/**
* @deprecated currentStep 필드를 사용하세요. 이 필드는 하위 호환성을 위해 유지됩니다.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,8 @@ public class ChatConversation {

@CreatedDate
private LocalDateTime createdAt;

// refactor#256 - metadata 필드 추가
@Column(columnDefinition = "TEXT")
private String metadata;
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ public interface ChatConversationRepository extends JpaRepository<ChatConversati

List<ChatConversation> findByUserIdOrderByCreatedAtDesc(Long userId);

List<ChatConversation> findByUserIdOrderByCreatedAtAsc(Long userId);

List<ChatConversation> findTop20ByUserIdOrderByCreatedAtDesc(Long userId);

boolean existsByUserIdAndMessage(Long userId, String message);
Expand Down
94 changes: 88 additions & 6 deletions src/main/java/com/back/domain/chatbot/service/ChatbotService.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import com.back.domain.cocktail.enums.AlcoholBaseType;
import com.back.domain.cocktail.enums.AlcoholStrength;
import com.back.domain.cocktail.repository.CocktailRepository;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -26,12 +27,14 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StreamUtils;
import com.fasterxml.jackson.core.JsonProcessingException;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
Expand All @@ -43,6 +46,8 @@ public class ChatbotService {
private final ChatConversationRepository chatConversationRepository;
private final CocktailRepository cocktailRepository;

private final ObjectMapper objectMapper = new ObjectMapper(); // JSON 변환용

@Value("classpath:prompts/chatbot-system-prompt.txt")
private Resource systemPromptResource;

Expand Down Expand Up @@ -87,6 +92,8 @@ public void init() throws IOException {

@Transactional
public ChatResponseDto sendMessage(ChatRequestDto requestDto) {
saveUserMessage(requestDto);

try {
Integer currentStep = requestDto.getCurrentStep();

Expand Down Expand Up @@ -185,8 +192,47 @@ else if (currentStep >= 1 && currentStep <= 4) {
}
}

// ============ 수정된 메서드들 ============
private void saveUserMessage(ChatRequestDto requestDto) {
String metadata = null;
if (requestDto.getSelectedValue() != null) {
try {
// 사용자가 선택한 실제 값(value)을 JSON으로 저장
metadata = objectMapper.writeValueAsString(Map.of("selectedValue", requestDto.getSelectedValue()));
} catch (JsonProcessingException e) {
log.error("사용자 선택 값 JSON 직렬화 실패", e);
}
}

ChatConversation userMessage = ChatConversation.builder()
.userId(requestDto.getUserId())
.message(requestDto.getMessage()) // 사용자가 본 텍스트(label)
.sender(MessageSender.USER)
.createdAt(LocalDateTime.now())
.metadata(metadata) // 선택한 실제 값(value)
.build();
chatConversationRepository.save(userMessage);
}

private ChatConversation saveBotResponse(Long userId, String message, Object stepData) {
String metadata = null;
if (stepData != null) {
try {
// 봇이 보낸 옵션, 카드 등 구조화된 데이터를 JSON으로 저장
metadata = objectMapper.writeValueAsString(stepData);
} catch (JsonProcessingException e) {
log.error("봇 응답 메타데이터 JSON 직렬화 실패", e);
}
}

ChatConversation botResponse = ChatConversation.builder()
.userId(userId)
.message(message)
.sender(MessageSender.CHATBOT)
.createdAt(LocalDateTime.now())
.metadata(metadata)
.build();
return chatConversationRepository.save(botResponse);
}
/**
* 대화 컨텍스트 빌드 - 변경사항: sender로 구분하여 대화 재구성
*/
Expand Down Expand Up @@ -242,8 +288,44 @@ public ChatConversation saveConversation(ChatRequestDto requestDto, String respo
* 사용자 채팅 기록 조회 - 변경사항: sender 구분 없이 모든 메시지 시간순으로 조회
*/
@Transactional(readOnly = true)
public List<ChatConversation> getUserChatHistory(Long userId) {
return chatConversationRepository.findByUserIdOrderByCreatedAtDesc(userId);
public List<ChatResponseDto> getUserChatHistory(Long userId) {
List<ChatConversation> history = chatConversationRepository.findByUserIdOrderByCreatedAtAsc(userId); // 시간순으로 변경

return history.stream().map(conversation -> {
ChatResponseDto.ChatResponseDtoBuilder builder = ChatResponseDto.builder()
.id(conversation.getId())
.userId(conversation.getUserId())
.message(conversation.getMessage())
.sender(conversation.getSender())
.createdAt(conversation.getCreatedAt());

String metadata = conversation.getMetadata();
if (metadata != null && !metadata.isEmpty()) {
try {
if (conversation.getSender() == MessageSender.CHATBOT) {
StepRecommendationResponseDto stepData = objectMapper.readValue(metadata, StepRecommendationResponseDto.class);
builder.stepData(stepData);

if (stepData.getOptions() != null && !stepData.getOptions().isEmpty()) {
builder.type(MessageType.RADIO_OPTIONS);
} else if (stepData.getRecommendations() != null && !stepData.getRecommendations().isEmpty()) {
builder.type(MessageType.CARD_LIST);
} else {
builder.type(MessageType.TEXT);
}
} else { // sender == USER
// 사용자 메시지의 메타데이터는 FE에서 선택 처리 등에 활용 가능
builder.type(MessageType.TEXT);
}
} catch (JsonProcessingException e) {
log.error("대화 기록 metadata 역직렬화 실패 [ID: {}]: {}", conversation.getId(), e.getMessage());
builder.type(MessageType.TEXT);
}
} else {
builder.type(MessageType.TEXT);
}
return builder.build();
}).collect(Collectors.toList());
}

/**
Expand Down Expand Up @@ -429,8 +511,8 @@ private ChatConversation generateAIResponse(ChatRequestDto requestDto) {
// 응답 후처리
response = postProcessResponse(response, messageType);

// 대화 저장 - 사용자 메시지와 봇 응답을 각각 저장하고 저장된 봇 응답 반환
return saveConversation(requestDto, response);
// 봇 응답만 저장 -> 사용자 메시지는 sendmessage()에서 이미 저장됨
return saveBotResponse(requestDto.getUserId(), response, null);
}

/**
Expand Down Expand Up @@ -584,7 +666,7 @@ private ChatResponseDto handleStepRecommendation(ChatRequestDto requestDto) {
.sender(MessageSender.CHATBOT)
.createdAt(LocalDateTime.now())
.build();
ChatConversation savedResponse = chatConversationRepository.save(botResponse);
ChatConversation savedResponse = saveBotResponse(requestDto.getUserId(), message, stepData);

// 메타데이터 포함
ChatResponseDto.MetaData metaData = ChatResponseDto.MetaData.builder()
Expand Down