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 @@ -12,8 +12,6 @@
import hanium.modic.backend.common.error.exception.AppException;
import hanium.modic.backend.common.response.PageResponse;
import hanium.modic.backend.common.util.KeyGenerator;
import hanium.modic.backend.web.ai.aiChat.dto.request.ChatMessageRequest;
import hanium.modic.backend.web.ai.aiChat.dto.response.ChatMessageResponse;
import hanium.modic.backend.domain.ai.aiChat.entity.AiChatMessageEntity;
import hanium.modic.backend.domain.ai.aiChat.entity.AiChatRoomEntity;
import hanium.modic.backend.domain.ai.aiChat.repository.AiChatMessageRepository;
Expand All @@ -23,6 +21,8 @@
import hanium.modic.backend.domain.ai.aiServer.enums.SenderType;
import hanium.modic.backend.domain.ai.aiServer.repository.AiChatImageRepository;
import hanium.modic.backend.domain.ai.aiServer.service.AiServerService;
import hanium.modic.backend.web.ai.aiChat.dto.request.ChatMessageRequest;
import hanium.modic.backend.web.ai.aiChat.dto.response.ChatMessageResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

Expand Down Expand Up @@ -51,7 +51,6 @@ public class AiChatMessageService {

private final KeyGenerator keyGenerator;


// 사용자의 메시지,이미지 저장 후 AI 요청
@Transactional
public ChatMessageResponse sendUserMessage(Long userId, Long postId, ChatMessageRequest request) {
Expand Down Expand Up @@ -97,7 +96,7 @@ public ChatMessageResponse sendUserMessage(Long userId, Long postId, ChatMessage
// Ai 요청
aiServerService.processAiRequest(userId, message, aiChatImages);

return ChatMessageResponse.from(message);
return ChatMessageResponse.of(message, aiChatImageService.createImageGetUrl(request.aiChatImageId()));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

null 안전성 문제를 수정해야 합니다.

request.aiChatImageId()가 null일 수 있는 상황에서 aiChatImageService.createImageGetUrl()을 호출하면 문제가 발생할 수 있습니다. 158-164번 라인의 패턴과 동일하게 null 체크를 수행해야 합니다.

다음 diff를 적용하여 수정하세요:

-	return ChatMessageResponse.of(message, aiChatImageService.createImageGetUrl(request.aiChatImageId()));
+	String imageUrl = request.aiChatImageId() != null 
+		? aiChatImageService.createImageGetUrl(request.aiChatImageId()) 
+		: null;
+	return ChatMessageResponse.of(message, imageUrl);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return ChatMessageResponse.of(message, aiChatImageService.createImageGetUrl(request.aiChatImageId()));
String imageUrl = request.aiChatImageId() != null
? aiChatImageService.createImageGetUrl(request.aiChatImageId())
: null;
return ChatMessageResponse.of(message, imageUrl);
🤖 Prompt for AI Agents
In
src/main/java/hanium/modic/backend/domain/ai/aiChat/service/AiChatMessageService.java
around line 99, the code unconditionally calls
aiChatImageService.createImageGetUrl(request.aiChatImageId()) even though
request.aiChatImageId() can be null; mirror the null-safety pattern used at
lines 158-164 by checking if request.aiChatImageId() is non-null before calling
createImageGetUrl and pass null to ChatMessageResponse.of when the id is null,
ensuring no method is invoked with a null argument.

}

// 요청 메세지가 비어있는지 검증
Expand Down Expand Up @@ -142,7 +141,7 @@ public PageResponse<ChatMessageResponse> getMessages(
if (page == -1) {
// 마지막 페이지 조회를 위한 총 메시지 개수 계산
long totalMessages = aiChatMessageRepository.countByUserIdAndPostId(userId, postId);
page = (int) ((totalMessages - 1) / size); // 0-based index
page = (int)((totalMessages - 1) / size); // 0-based index
if (page < 0) {
page = 0; // 메시지가 없는 경우 첫 페이지로 설정
}
Expand All @@ -157,11 +156,11 @@ public PageResponse<ChatMessageResponse> getMessages(
Page<ChatMessageResponse> responsePage = messagePage.map(msg -> {
// 이미지 없는 경우
if (msg.getAiChatImageId() == null) {
return ChatMessageResponse.from(msg, null);
return ChatMessageResponse.from(msg);
}
// 이미지 있는 경우, URL 생성
String imageUrl = aiChatImageService.createImageGetUrl(msg.getAiChatImageId());
return ChatMessageResponse.from(msg, imageUrl);
return ChatMessageResponse.of(msg, imageUrl);
});

// PageResponse로 감싸서 반환
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ private void handleSuccessImageGeneration(
// 6.클라이언트는 이미지 생성 요청 후 SSE 연결을 맺어, SSE 연결 객체가 아래 Service에 존재한다. 이를 사용해 이미지를 응답한다.
aiResponseSseService.sendToClient(
message.requestId(),
ChatMessageResponse.from(responseChatMessage, imageUrl)
ChatMessageResponse.of(responseChatMessage, imageUrl)
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import hanium.modic.backend.common.amqp.service.MessageQueueService;
import hanium.modic.backend.common.error.ErrorCode;
import hanium.modic.backend.common.error.exception.AppException;
import hanium.modic.backend.domain.ai.aiChat.service.AiChatImageService;
import hanium.modic.backend.domain.ai.aiChat.service.AiChatMessageOrderService;
import hanium.modic.backend.web.ai.aiChat.dto.response.ChatMessageResponse;
import hanium.modic.backend.domain.ai.aiChat.entity.AiChatMessageEntity;
Expand All @@ -41,17 +42,24 @@
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
public class AiServerService {

// message 관련
private final MessageQueueService messageQueueService;
private final PostImageEntityRepository postImageEntityRepository;
private final AiResponseSseService aiResponseSseService;
private final AiChatService aiChatService;
private final AiChatMessageRepository aiChatMessageRepository;
private final AiChatMessageOrderService aiChatMessageOrderService;

// aiChatRoom 관련
private final AiChatRoomService aiChatRoomService;
private final AiChatRoomRepository aiChatRoomRepository;

// Image 관련
private final AiImagePermissionService aiImagePermissionService;
private final AiChatImageRepository aiChatImageRepository;
private final AiChatMessageRepository aiChatMessageRepository;
private final PostImageEntityRepository postImageEntityRepository;
private final AiChatImageService aiChatImageService;

private final ObjectMapper objectMapper;
private final AiResponseSseService aiResponseSseService;
private final AiChatService aiChatService;
private final AiChatMessageOrderService aiChatMessageOrderService;

// AiAgent를 통해 해당 메시지 채팅응답용인지, 이미지 생성용인지 구분 후 처리
// 빠른 응답을 위해 비동기 처리, 응답은 SSE를 통해 클라이언트에 전달
Expand All @@ -60,7 +68,7 @@ public class AiServerService {
public void processAiRequest(
final Long nowUserId,
AiChatMessageEntity chatMessage,
List<AiChatImageEntity> aiChatImages
List<AiChatImageEntity> aiChatImages // 없으면 빈 리스트
) {
final Long postId = chatMessage.getPostId();

Expand All @@ -75,7 +83,7 @@ public void processAiRequest(

if (requestCategory == RequestCategory.CHAT_GENERATION) {
// 채팅응답일 경우 채팅 생성 요청
requestChatCreation(chatMessage);
requestChatCreation(chatMessage, aiChatImages);
} else {
// 이미지 생성용인 경우 이미지 생성 요청
requestImageCreation(chatMessage, aiChatImages, nowUserId);
Expand All @@ -95,11 +103,11 @@ private RequestCategory classifyRequestCategory(String message, boolean hasImage
The user can provide both text and/or an image.

Decide the intent:
- "IMAGE_GENERATION":
- "IMAGE_GENERATION":
* If the user only provides an image without text.
* If the text is asking to generate, modify, or create a new image.
* If the user provides both image and text, but the text still indicates a new image should be generated.
- "CHAT_GENERATION":
- "CHAT_GENERATION":
* If the user only wants a conversational response.
* If the user provides both image and text, but the text indicates normal chat about the image, not a request for new generation.

Expand All @@ -119,7 +127,8 @@ private RequestCategory classifyRequestCategory(String message, boolean hasImage

// 채팅응답일 경우 채팅 생성 요청
private void requestChatCreation(
AiChatMessageEntity chatMessage
AiChatMessageEntity chatMessage,
List<AiChatImageEntity> aiChatImages
) {
final Long userId = chatMessage.getUserId();
final Long postId = chatMessage.getPostId();
Expand Down Expand Up @@ -151,11 +160,18 @@ private void requestChatCreation(
// 3.채팅룸 요약 업데이트
aiChatRoomService.updateChatSummary(userId, postId, response.newSummary());

// 3. 사용자가 보낸 image 조회
String imageUrl;
if (aiChatImages.isEmpty()) {
imageUrl = null;
} else {
imageUrl = aiChatImageService.createImageGetUrl(aiChatImages.get(0).getId());
}

// 4. SSE로 실시간 응답
// TODO: 실시간 응답으로 바꿔야 함.
aiResponseSseService.sendToClient(
chatMessage.getRequestId(),
ChatMessageResponse.from(message)
ChatMessageResponse.of(message, imageUrl)
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public record ChatMessageResponse(
LocalDateTime createdAt,
AiImageStatus status // 이미지 상태
) {
public static ChatMessageResponse from(AiChatMessageEntity entity, String imageUrl) {
public static ChatMessageResponse of(AiChatMessageEntity entity, String imageUrl) {
return new ChatMessageResponse(
entity.getId(),
entity.getMessageOrder(),
Expand All @@ -33,7 +33,7 @@ public static ChatMessageResponse from(AiChatMessageEntity entity, String imageU
}

public static ChatMessageResponse from(AiChatMessageEntity entity) {
return from(entity, null);
return of(entity, null);
}

public static ChatMessageResponse createErrorResponse(AiChatMessageEntity entity, String errorMessage) {
Expand Down