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 @@ -5,6 +5,9 @@
import com.going.server.domain.chatbot.service.ChatbotService;
import com.going.server.global.response.SuccessResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
Expand All @@ -16,25 +19,78 @@
@RestController
@RequestMapping("/chatbot")
@RequiredArgsConstructor
@Tag(name = "[캡스톤]Chatbot", description = "챗봇 관련 통신을 위한 API")
@Tag(name = "[캡스톤]Chatbot", description = "챗봇 관련 기능 API")
public class ChatbotController {

private final ChatbotService chatbotService;

@PostMapping("/{graphId}/original")
@Operation(summary = "[챗봇] 원문 보기", description = "그래프에 포함된 원문 텍스트를 반환합니다.")
@ApiResponses({
@ApiResponse(responseCode = "201", description = "원문 반환 성공", content = @Content(schema = @Schema(implementation = CreateChatbotResponseDto.class))),
@ApiResponse(responseCode = "404", description = "그래프를 찾을 수 없음"),
@ApiResponse(responseCode = "500", description = "서버 오류")
})
public SuccessResponse<?> getOriginalText(@PathVariable String graphId) {
CreateChatbotResponseDto response = chatbotService.getOriginalText(graphId);
return SuccessResponse.of(response, "201");
}

@PostMapping("/{graphId}/summary")
@Operation(summary = "[챗봇] 요약본 생성하기", description = "그래프의 요약 정보를 생성합니다.")
@ApiResponses({
@ApiResponse(responseCode = "201", description = "요약본 생성 성공", content = @Content(schema = @Schema(implementation = CreateChatbotResponseDto.class))),
@ApiResponse(responseCode = "404", description = "그래프를 찾을 수 없음"),
@ApiResponse(responseCode = "500", description = "서버 오류")
})
public SuccessResponse<?> getSummaryText(@PathVariable String graphId) {
CreateChatbotResponseDto response = chatbotService.getSummaryText(graphId);
return SuccessResponse.of(response, "201");
}


@PostMapping("/{graphId}")
@Operation(summary = "[챗봇 화면] 챗봇 응답 생성", description = "챗봇 화면에서 사용자의 질문에 응답을 생성합니다.")
@ApiResponses(
@ApiResponse(
responseCode = "201",
description = "호출에 성공했습니다.",
content = @Content(
mediaType = "application/json",
schema = @Schema(example = "{\"message\":\"\"}")
)
)
@Operation(
summary = "[챗봇 기능] 챗봇 응답 생성",
description = """
사용자의 질문에 따라 다양한 응답 모드를 제공합니다.
- `default` : 일반 응답 (기존 GPT 방식)
- `rag` : 지식그래프 기반 RAG 응답
- `cartoon` : 4컷 만화 이미지 생성
- `video` : 질문 주제에 맞는 교육 영상 추천
"""
)
public SuccessResponse<?> generateChatbotAnswer(@PathVariable String graphId,
@RequestBody CreateChatbotRequestDto createChatbotRequestDto) {
CreateChatbotResponseDto result = chatbotService.createAnswer(graphId, createChatbotRequestDto);
@Parameters({
@Parameter(name = "graphId", in = ParameterIn.PATH, description = "그래프 ID", required = true),
@Parameter(name = "mode", in = ParameterIn.QUERY, description = "응답 모드 (default, rag, cartoon, video)", required = false)
})
@ApiResponses({
@ApiResponse(responseCode = "201", description = "응답 생성 성공", content = @Content(schema = @Schema(implementation = CreateChatbotResponseDto.class))),
@ApiResponse(responseCode = "400", description = "잘못된 요청"),
@ApiResponse(responseCode = "500", description = "서버 오류")
})
public SuccessResponse<?> generateChatbotAnswer(
@PathVariable String graphId,
@RequestParam(defaultValue = "default") String mode,
@RequestBody(required = false) CreateChatbotRequestDto dto
) {
CreateChatbotResponseDto result;

switch (mode) {
case "rag":
result = chatbotService.createAnswerWithRAG(graphId, dto);
break;
case "cartoon":
result = chatbotService.createCartoon(graphId, dto);
break;
case "video":
result = chatbotService.recommendVideo(graphId, dto);
break;
case "default":
default:
result = chatbotService.createSimpleAnswer(graphId, dto);
}

return SuccessResponse.of(result, "201");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,5 @@
public class CreateChatbotRequestDto {
@JsonProperty("isNewChat") // JSON 필드명과 강제 매핑
private boolean isNewChat; // 새로운 대화인지 여부

private String chatContent; // 채팅 내용
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public class CreateChatbotResponseDto {
private String graphId; // 지식그래프 ID
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm")
private LocalDateTime createdAt; // 응답 생성 시각
private List<String> retrievedChunks; // 후) RAG: 검색된 문장들
private List<String> sourceNodes; // 후) RAG: 참조된 지식그래프 노드 ID
private Map<String, String> ragMeta; // 후) RAG: 점수, 검색 method 등
private List<String> retrievedChunks; // RAG: 검색된 문장들
private List<String> sourceNodes; // RAG: 참조된 지식그래프 노드 ID
private Map<String, String> ragMeta; // RAG: 점수, 검색 method 등
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@
import com.going.server.domain.chatbot.dto.CreateChatbotResponseDto;

public interface ChatbotService {
// 원문 반환
CreateChatbotResponseDto getOriginalText(String graphId);
// 요약본 생성
CreateChatbotResponseDto getSummaryText(String graphId);
// RAG 챗봇 응답 생성
CreateChatbotResponseDto createAnswer(String graphStrId, CreateChatbotRequestDto createChatbotRequestDto);
CreateChatbotResponseDto createAnswerWithRAG(String graphStrId, CreateChatbotRequestDto createChatbotRequestDto);
// RAG 사용하지 않는 응답 생성
CreateChatbotResponseDto createSimpleAnswer(String graphId, CreateChatbotRequestDto createChatbotRequestDto);
// 4컷만화 생성
CreateChatbotResponseDto createCartoon(String graphId, CreateChatbotRequestDto createChatbotRequestDto);
// 추천 영상 생성
CreateChatbotResponseDto recommendVideo(String graphId, CreateChatbotRequestDto createChatbotRequestDto);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,14 @@
import com.going.server.domain.chatbot.repository.ChattingRepository;
import com.going.server.domain.graph.entity.Graph;
import com.going.server.domain.graph.entity.GraphNode;
import com.going.server.domain.graph.exception.GraphContentNotFoundException;
import com.going.server.domain.graph.repository.GraphRepository;
import com.going.server.domain.graph.repository.GraphNodeRepository;
import com.going.server.domain.openai.service.AnswerCreateService;
import com.going.server.domain.openai.dto.ImageCreateRequestDto;
import com.going.server.domain.openai.service.ImageCreateService;
import com.going.server.domain.openai.service.RAGAnswerCreateService;
import com.going.server.domain.openai.service.SimpleAnswerCreateService;
import com.going.server.domain.openai.service.TextSummaryCreateService;
import com.going.server.domain.rag.service.SimilarityFilterService;
import com.going.server.domain.rag.util.PromptBuilder;
import lombok.RequiredArgsConstructor;
Expand All @@ -27,14 +32,49 @@
public class ChatbotServiceImpl implements ChatbotService {
private final GraphRepository graphRepository;
private final ChattingRepository chattingRepository;
private final AnswerCreateService answerCreateService;
private final GraphNodeRepository graphNodeRepository;
private final SimilarityFilterService similarityFilterService;
private final PromptBuilder promptBuilder;
// openai 관련 service
private final TextSummaryCreateService textSummaryCreateService;
private final SimpleAnswerCreateService simpleAnswerCreateService;
private final RAGAnswerCreateService ragAnswerCreateService;
private final ImageCreateService imageCreateService;

// 챗봇 응답 생성
// 원문 반환
@Override
public CreateChatbotResponseDto createAnswer(String graphStrId, CreateChatbotRequestDto createChatbotRequestDto) {
public CreateChatbotResponseDto getOriginalText(String graphId) {
Graph graph = graphRepository.getByGraph(Long.valueOf(graphId));

return CreateChatbotResponseDto.builder()
.chatContent(graph.getContent()) // 원문 텍스트
.graphId(graphId)
.createdAt(LocalDateTime.now())
.build();
}

// 요약본 생성
@Override
public CreateChatbotResponseDto getSummaryText(String graphId) {
Graph graph = graphRepository.getByGraph(Long.valueOf(graphId));

String context = Optional.ofNullable(graph.getContent())
.filter(s -> !s.trim().isEmpty())
.orElseThrow(GraphContentNotFoundException::new);

String summary = textSummaryCreateService.summarize(context);

return CreateChatbotResponseDto.builder()
.chatContent(summary) // 요약 텍스트
.graphId(graphId)
.createdAt(LocalDateTime.now())
.build();
}


// RAG 챗봇 응답 생성
@Override
public CreateChatbotResponseDto createAnswerWithRAG(String graphStrId, CreateChatbotRequestDto createChatbotRequestDto) {
Long graphId = Long.valueOf(graphStrId);

// 404 : 지식그래프 찾을 수 없음
Expand Down Expand Up @@ -96,10 +136,10 @@ public CreateChatbotResponseDto createAnswer(String graphStrId, CreateChatbotReq
System.out.println("[DEBUG] matchedNodes.size(): " + matchedNodes.size());
System.out.println("[DEBUG] candidateSentences.size(): " + candidateSentences.size());
System.out.println("[DEBUG] filteredChunks.size(): " + filteredChunks.size());
chatContent = answerCreateService.chat(chatHistory, newChat);
chatContent = ragAnswerCreateService.chat(chatHistory, newChat);
} else {
System.out.println("[INFO] RAG 적용됨 - 유사 문장 " + retrievedChunks.size() + "개 포함");
chatContent = answerCreateService.chatWithContext(chatHistory, finalPrompt);
chatContent = ragAnswerCreateService.chatWithContext(chatHistory, finalPrompt);
}

// 응답 repository에 저장
Expand All @@ -122,6 +162,123 @@ public CreateChatbotResponseDto createAnswer(String graphStrId, CreateChatbotReq
.build();
}

// RAG 사용하지 않는 응답 생성
@Override
public CreateChatbotResponseDto createSimpleAnswer(String graphStrId, CreateChatbotRequestDto createChatbotRequestDto) {
Long graphId = Long.valueOf(graphStrId);

// 404 : 지식그래프 찾을 수 없음
Graph graph = graphRepository.getByGraph(graphId);

// 새로운 대화인 경우 기존 채팅 삭제
if (createChatbotRequestDto.isNewChat()) {
deletePreviousChat(graphId);
}

// 기존 채팅 내역 조회
List<Chatting> chatHistory = chattingRepository.findAllByGraphId(graphId);

// 사용자 입력 채팅
String newChat = createChatbotRequestDto.getChatContent();

// 채팅 저장 (USER)
Chatting userChat = Chatting.builder()
.graph(graph)
.content(newChat)
.sender(Sender.USER)
.createdAt(LocalDateTime.now())
.build();
chattingRepository.save(userChat);

// GPT-3.5로 응답 생성 (RAG 없이)
String chatContent = simpleAnswerCreateService.simpleChat(chatHistory, newChat);

// 채팅 저장 (GPT)
Chatting gptChat = Chatting.builder()
.graph(graph)
.content(chatContent)
.sender(Sender.GPT)
.createdAt(LocalDateTime.now())
.build();
chattingRepository.save(gptChat);

// 응답 반환
return CreateChatbotResponseDto.builder()
.chatContent(chatContent)
.graphId(graphStrId)
.createdAt(gptChat.getCreatedAt())
.build();
}

/// 4컷만화 생성
@Override
public CreateChatbotResponseDto createCartoon(String graphId, CreateChatbotRequestDto createChatbotRequestDto) {
String concept = createChatbotRequestDto.getChatContent(); // 사용자 질문 → 개념

// 개선된 프롬프트
String prompt = """
당신은 초등학생부터 고등학생까지의 학습자를 위한 교육용 4컷 만화를 그리는 일러스트레이터입니다.

---

📌 개념: %s

---

📋 작업 순서:

1. 위에 주어진 개념을 먼저 **충분히 이해**하세요.
- 해당 개념의 **의미, 특징, 맥락, 예시**를 상상하고 파악하세요.
- 개념에 대한 핵심 요소를 추출하여 시각적으로 설명 가능한 흐름을 구성하세요.

2. 다음 기준에 따라 **한 장의 이미지에 2x2 그리드(총 4컷)**로 그림을 설계하세요.
- 왼쪽 위: 장면 1
- 오른쪽 위: 장면 2
- 왼쪽 아래: 장면 3
- 오른쪽 아래: 장면 4

3. **절대 텍스트(한글, 영어, 숫자 등 어떤 형태든 포함 금지)**를 넣지 마세요.
- 라벨, 말풍선, 자막, 글자처럼 보일 수 있는 시각 요소도 금지
- **오직 시각적 표현만으로** 의미가 전달되어야 합니다

4. 스타일 조건:
- **파스텔톤의 부드럽고 따뜻한 색상**
- **iOS 이모지 스타일** 또는 **플랫한 웹툰 스타일**
- 복잡한 배경 없이 **간단한 배경과 상징**으로 표현
- 필요한 경우 **화살표, 흐름선, 상징 기호**를 적절히 사용 (과도하게 사용하지 않음)

---

🎯 목적:
글로만 이해하기 어려운 개념을 **누구나 직관적으로 이해할 수 있도록 시각적으로 구성된 4컷 만화로 전달**하는 것입니다.
""".formatted(concept);

// DALL-E 이미지 요청 DTO 생성
ImageCreateRequestDto requestDto = new ImageCreateRequestDto(
prompt,
"dall-e-3",
"vivid",
"1024x1024",
1
);

// 이미지 생성
String imageUrl = imageCreateService.generatePicture(requestDto);

// 응답 DTO 생성
return CreateChatbotResponseDto.builder()
.chatContent(imageUrl)
.graphId(graphId)
.createdAt(LocalDateTime.now())
.build();
}

// 추천 영상 생성
@Override
public CreateChatbotResponseDto recommendVideo(String graphId, CreateChatbotRequestDto createChatbotRequestDto) {
return null;
}

// 채팅 삭제 메서드
private void deletePreviousChat(Long graphId) {
chattingRepository.deleteByGraphId(graphId);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.going.server.domain.graph.exception;

import com.going.server.global.exception.BaseException;

public class GraphContentNotFoundException extends BaseException {
public GraphContentNotFoundException() {
super(GraphErrorCode.GRAPH_CONTENT_EMPTY);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
@Getter
@AllArgsConstructor
public enum GraphErrorCode implements BaseErrorCode {
GRAPH_NOT_FOUND(NOT_FOUND, "GRAPH_404_1", "존재하지 않는 graph_id 입니다.");
GRAPH_NOT_FOUND(NOT_FOUND, "GRAPH_404_1", "존재하지 않는 graph_id 입니다."),
GRAPH_CONTENT_EMPTY(NOT_FOUND, "GRAPH_404_2", "그래프에 저장된 원문 텍스트가 존재하지 않습니다.");

private final int httpStatus;
private final String code;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,4 @@ public class ImageCreateRequestDto {
private String style = "vivid";
private String size = "1024x1024";
private int n = 1;

public ImageCreateRequestDto(String prompt) {
this.prompt = prompt;
}
}
Loading