Repository: knu-Capstone2025/Capstone-Interview-Assistant
Pull request #48: feat: 대화 히스토리를 ChatService로 이동하고, 로그 관리
Author: login: choi-sunggyu
Labels: name: ✨ Feature
description: 새로운 기능 추가 및 구현하는 경우
Created: 2025-04-27T16:03:59Z | Merged: 2025-04-29T10:12:08Z

## #️⃣ 연관된 이슈
> #47 


## 📝 작업 내용
> 이번 PR에서 작업한 내용을 간략히 설명해주세요.(이미지 첨부 가능)
- 이전 대화를 히스토리 객체에 저장하고 관리합니다.
- ChatService.cs에서 대화 히스토리 관리할 수 있도록 ChatHistory.cs 추가하였습니다.

### 스크린샷 (선택)
기존 응답
![a는 몇](https://github.com/user-attachments/assets/8bc8ce78-16ca-4d03-b146-b3ac334e3e55)
수정된 코드에서의 응답
![a를 기억중](https://github.com/user-attachments/assets/94aa1514-4163-4a8d-b6dd-e00bc18a96b3)

## 💬 리뷰 요구사항(선택)

> ChatService내에서 끝낼 수 있는 작업을 이후에 확장될걸 고려해서 ChatHistory를 생성했습니다.
고려했던 확장 내용은 질문 유형구분, 피드백정도로 생각 중입니다. 혹시 확장할 필요가 없을까요?

## ⏰ 현재 버그


## ✏ Git Close
> close #47 


Linked issue #47: Feat: 대화 히스토리 저장 및 백엔드 전송 기능 추가
## 🧐 어떤 기능인가요?

> 대화를 담는 히스토리 객체를 생성하고 백엔드로 넘겨줍니다.

## 🔧 작업 상세 내용
- [x] 대화 히스토리 객체 생성
- [x] 히스토리가 백엔드로 전송되는지 확인

## 📆 예상 기간

> 4월 26일~28일

## 📙 참고할만한 자료(선택)


Diff:
diff --git a/src/InterviewAssistant.Web/Components/Pages/Home.razor b/src/InterviewAssistant.Web/Components/Pages/Home.razor
index eaab1cb..72cb6db 100644
--- a/src/InterviewAssistant.Web/Components/Pages/Home.razor
+++ b/src/InterviewAssistant.Web/Components/Pages/Home.razor
@@ -164,7 +164,6 @@
         var userMessage = new ChatMessage { Role = MessageRoleType.User, Message = userInput };
         messages.Add(userMessage);
 
-        var currentInput = userInput;
         userInput = string.Empty;
 
         await ScrollToBottom();
@@ -175,7 +174,7 @@
             StateHasChanged();
 
             // ChatService를 통해 응답 가져오기
-            var responses = ChatService.SendMessageAsync(currentInput);
+            var responses = ChatService.SendMessageAsync(messages);
 
             var assistantMessage = new ChatMessage { Role = MessageRoleType.Assistant, Message = string.Empty };
             messages.Add(assistantMessage);
diff --git a/src/InterviewAssistant.Web/Services/ChatService.cs b/src/InterviewAssistant.Web/Services/ChatService.cs
index 51c5549..920cb91 100644
--- a/src/InterviewAssistant.Web/Services/ChatService.cs
+++ b/src/InterviewAssistant.Web/Services/ChatService.cs
@@ -11,9 +11,9 @@ public interface IChatService
     /// <summary>
     /// 사용자 메시지를 처리하고 응답을 반환합니다.
     /// </summary>
-    /// <param name="message">사용자 메시지</param>
+    /// <param name="messages">사용자 메시지</param>
     /// <returns>처리된 챗봇 응답</returns>
-    IAsyncEnumerable<ChatResponse> SendMessageAsync(string message);
+    IAsyncEnumerable<ChatResponse> SendMessageAsync(IEnumerable<ChatMessage> messages);
 }
 
 /// <summary>
@@ -26,26 +26,36 @@ public class ChatService(IChatApiClient client, ILoggerFactory loggerFactory) :
                                                     .CreateLogger<ChatService>();
     
     /// <inheritdoc/>
-    public async IAsyncEnumerable<ChatResponse> SendMessageAsync(string message)
+    public async IAsyncEnumerable<ChatResponse> SendMessageAsync(IEnumerable<ChatMessage> messages)
     {
-        if (string.IsNullOrWhiteSpace(message))
+        if (messages == null || !messages.Any())
         {
-            _logger.LogWarning("빈 메시지가 전송되었습니다.");
+            _logger.LogWarning("빈 메시지 목록이 전송되었습니다.");
             yield break;
         }
         
-        _logger.LogInformation("메시지 처리 시작: {Message}", message);
+        _logger.LogInformation("메시지 목록 처리 시작: {Count}개", messages.Count());
 
-        // 현재는 메세지 트림만 처리한다
-        string processedMessage = message.Trim();
+        // 메시지 목록 정리 (필요 시 트림 등 추가 가능)
+        var processedMessages = messages
+            .Where(m => string.IsNullOrWhiteSpace(m.Message.Trim()) == false)
+            .Select(m => new ChatMessage
+            {
+                Role = m.Role,
+                Message = m.Message.Trim()
+            })
+            .ToList();
+
+        if (processedMessages.Count == 0)
+        {
+            _logger.LogWarning("모든 메시지가 비어있습니다.");
+            yield break;
+        }
                     
         // API 요청 생성 및 전송 (현재는 하드코딩된 응답을 반환하는 ChatApiClient 사용)
         var request = new ChatRequest
-        { 
-            Messages =
-            [
-                new ChatMessage { Role = MessageRoleType.User, Message = processedMessage }
-            ]
+        {
+            Messages = processedMessages
         };
         var responses = _client.SendMessageAsync(request);
         
@@ -54,6 +64,6 @@ public async IAsyncEnumerable<ChatResponse> SendMessageAsync(string message)
             yield return response;
         }
 
-        _logger.LogInformation("메시지 처리 완료");
+        _logger.LogInformation("메시지 목록 처리 완료");
     }
-}
+}
\ No newline at end of file
diff --git a/test/InterviewAssistant.Web.Tests/Services/ChatServiceTests.cs b/test/InterviewAssistant.Web.Tests/Services/ChatServiceTests.cs
index f69ce9d..a83774a 100644
--- a/test/InterviewAssistant.Web.Tests/Services/ChatServiceTests.cs
+++ b/test/InterviewAssistant.Web.Tests/Services/ChatServiceTests.cs
@@ -31,32 +31,58 @@ public void Cleanup()
         _loggerFactory?.Dispose();
     }
 
-    // 테스트 1: 메시지 전송이 성공할 때 응답을 올바르게 반환하는지 검증
+    // 테스트 1:  메시지 목록이 정상적으로 처리될 때 응답 반환
     [Test]
     public async Task SendMessageAsync_WhenSuccessful_ReturnsResponse()
     {
-        // Arrange - 대체 API 클라이언트가 성공적인 응답을 반환하도록 설정
+        // Arrange - 테스트 데이터 설정
+        var messages = new List<ChatMessage>
+        {
+            new ChatMessage { Role = MessageRoleType.User, Message = "안녕하세요" }
+        };
         var expectedResponse = new ChatResponse { Message = "테스트 응답입니다" };
-        _apiClient.SendMessageAsync(Arg.Any<ChatRequest>()).Returns(new List<ChatResponse>() { expectedResponse }.ToAsyncEnumerable());
-        
-        // Act - ChatService의 메시지 전송 메서드 호출
-        var result = await _chatService.SendMessageAsync("안녕하세요").ToListAsync();
-        
+        _apiClient.SendMessageAsync(Arg.Any<ChatRequest>())
+            .Returns(new List<ChatResponse> { expectedResponse }.ToAsyncEnumerable());
+
+        // Act - 메시지 목록을 API에 전송
+        var result = await _chatService.SendMessageAsync(messages).ToListAsync();
+
         // Assert - 결과 검증
         result.ShouldNotBeNull();
+        result.Count.ShouldBe(1);
         result[0].Message.ShouldBe("테스트 응답입니다");
     }
 
-    // 테스트 2: 메시지 전송이 실패할 때 예외 처리하는지 검증
+    // 테스트 2: 빈 메시지 목록이 전달될 때 처리 중단
+    [Test]
+    public async Task SendMessageAsync_WhenMessagesAreEmpty_YieldsNoResponses()
+    {
+        // Arrange - 메세지 목록을 빈 리스트로 설정
+        var messages = new List<ChatMessage>();
+
+        // Act - 빈 메시지 목록을 전달
+        var result = await _chatService.SendMessageAsync(messages).ToListAsync();
+
+        // Assert - 빈 리스트가 반환되는지 검증
+        result.ShouldBeEmpty();
+    }
+
+
+    // 테스트 3: 메시지 전송이 실패할 때 예외 처리하는지 검증
     [Test]
     public void SendMessageAsync_WhenApiFails_ThrowsException()
     {
         // Arrange - 대체 API 클라이언트가 예외를 발생하도록 설정
-        _apiClient.SendMessageAsync(Arg.Any<ChatRequest>()).Throws(new Exception("API 호출 실패"));
+        var messages = new List<ChatMessage>
+        {
+            new ChatMessage { Role = MessageRoleType.User, Message = "안녕하세요" }
+        };
+        _apiClient.SendMessageAsync(Arg.Any<ChatRequest>())
+            .Throws(new Exception("API 호출 실패"));
 
         // Act & Assert - 예외가 발생하는지 검증
         var ex = Assert.ThrowsAsync<Exception>(async () =>
-            await _chatService.SendMessageAsync("안녕하세요").ToListAsync());
+            await _chatService.SendMessageAsync(messages).ToListAsync());
 
         Assert.That(ex.Message, Is.EqualTo("API 호출 실패"));
     }


Reviews:
user: login: justinyoo
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-28T11:40:25Z
user: login: justinyoo
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-29T07:12:29Z
user: login: choi-sunggyu
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-29T07:44:00Z
user: login: choi-sunggyu
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-29T08:00:29Z
user: login: justinyoo
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-29T10:12:01Z

Review comments:
path: src/InterviewAssistant.Web/Services/ChatService.cs
user: login: justinyoo
body: 이 파일이 아니라 `Home.razor` 파일을 수정해야 해요. 여기서 `.SendMessageAsync(userInput);` 에서 현재 메시지만 보내는데, 여기서 전체 히스토리를 보내는 식으로 해야 합니다.

https://github.com/knu-Capstone2025/Capstone-Interview-Assistant/blob/779ac22bab3f66b0faa597901d2d91b72ac83419/src/InterviewAssistant.Web/Components/Pages/Home.razor#L159-L210
created_at: 2025-04-28T11:40:06Z
author_association: CONTRIBUTOR
path: src/InterviewAssistant.Common/Models/ChatHistory.cs
user: login: justinyoo
body: 그러면 굳이 이 파일이 필요 없음
created_at: 2025-04-28T11:40:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -161,10 +161,11 @@
         if (string.IsNullOrWhiteSpace(userInput) || isLoading)
             return;
 
-        var userMessage = new ChatMessage { Role = MessageRoleType.User, Message = userInput };
-        messages.Add(userMessage);
+        messages.Add(new ChatMessage { 
+            Role = MessageRoleType.User, 
+            Message = userInput 
+        });
path: src/InterviewAssistant.Web/Components/Pages/Home.razor
user: login: justinyoo
body: 여긴 아래 `assistantMessage` 부분과 비교해 보면 이전 코드가 일관성 측면에서 낫습니다.
created_at: 2025-04-29T07:05:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -11,9 +11,9 @@ public interface IChatService
     /// <summary>
     /// 사용자 메시지를 처리하고 응답을 반환합니다.
     /// </summary>
-    /// <param name="message">사용자 메시지</param>
+    /// <param name="messages">사용자 메시지</param>
     /// <returns>처리된 챗봇 응답</returns>
-    IAsyncEnumerable<ChatResponse> SendMessageAsync(string message);
+    IAsyncEnumerable<ChatResponse> SendMessageAsync(List<ChatMessage> messages);
path: src/InterviewAssistant.Web/Services/ChatService.cs
user: login: justinyoo
body: `List<>` 대신 `IEnumerable<>`을 써 보세요.
created_at: 2025-04-29T07:06:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -26,26 +26,36 @@ public class ChatService(IChatApiClient client, ILoggerFactory loggerFactory) :
                                                     .CreateLogger<ChatService>();
     
     /// <inheritdoc/>
-    public async IAsyncEnumerable<ChatResponse> SendMessageAsync(string message)
+    public async IAsyncEnumerable<ChatResponse> SendMessageAsync(List<ChatMessage> messages)
path: src/InterviewAssistant.Web/Services/ChatService.cs
user: login: justinyoo
body: 여기도 `IEnumerable<>`로...
created_at: 2025-04-29T07:07:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -26,26 +26,36 @@ public class ChatService(IChatApiClient client, ILoggerFactory loggerFactory) :
                                                     .CreateLogger<ChatService>();
     
     /// <inheritdoc/>
-    public async IAsyncEnumerable<ChatResponse> SendMessageAsync(string message)
+    public async IAsyncEnumerable<ChatResponse> SendMessageAsync(List<ChatMessage> messages)
     {
-        if (string.IsNullOrWhiteSpace(message))
+        if (messages == null || messages.Count == 0)
         {
-            _logger.LogWarning("빈 메시지가 전송되었습니다.");
+            _logger.LogWarning("빈 메시지 목록이 전송되었습니다.");
path: src/InterviewAssistant.Web/Services/ChatService.cs
user: login: justinyoo
body: Warning vs Error? 어떤 게 더 정확할까요?
created_at: 2025-04-29T07:08:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -26,26 +26,36 @@ public class ChatService(IChatApiClient client, ILoggerFactory loggerFactory) :
                                                     .CreateLogger<ChatService>();
     
     /// <inheritdoc/>
-    public async IAsyncEnumerable<ChatResponse> SendMessageAsync(string message)
+    public async IAsyncEnumerable<ChatResponse> SendMessageAsync(List<ChatMessage> messages)
     {
-        if (string.IsNullOrWhiteSpace(message))
+        if (messages == null || messages.Count == 0)
         {
-            _logger.LogWarning("빈 메시지가 전송되었습니다.");
+            _logger.LogWarning("빈 메시지 목록이 전송되었습니다.");
             yield break;
         }
         
-        _logger.LogInformation("메시지 처리 시작: {Message}", message);
+        _logger.LogInformation("메시지 목록 처리 시작: {Count}개", messages.Count);
 
-        // 현재는 메세지 트림만 처리한다
-        string processedMessage = message.Trim();
+        // 메시지 목록 정리 (필요 시 트림 등 추가 가능)
+        var processedMessages = messages
+            .Where(m => !string.IsNullOrWhiteSpace(m.Message))
path: src/InterviewAssistant.Web/Services/ChatService.cs
user: login: justinyoo
body: - 가독성을 고려한다면 `!string.IsNullOrWhiteSpace(m.Message)` 보다는 `string.IsNullOrWhiteSpace(m.Message) == false`가 더 나을 거예요.
- 그리고 마찬가지로 `m.Message.Trim()`을 해야 걸러질 것 같은데?
created_at: 2025-04-29T07:10:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -11,9 +11,9 @@ public interface IChatService
     /// <summary>
     /// 사용자 메시지를 처리하고 응답을 반환합니다.
     /// </summary>
-    /// <param name="message">사용자 메시지</param>
+    /// <param name="messages">사용자 메시지</param>
     /// <returns>처리된 챗봇 응답</returns>
-    IAsyncEnumerable<ChatResponse> SendMessageAsync(string message);
+    IAsyncEnumerable<ChatResponse> SendMessageAsync(List<ChatMessage> messages);
path: src/InterviewAssistant.Web/Services/ChatService.cs
user: login: choi-sunggyu
body: 수정이 필요없는 데이터이고 메모리 사용이 효율적이게 되겠네요! 수정하겠습니다!
created_at: 2025-04-29T07:44:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -26,26 +26,36 @@ public class ChatService(IChatApiClient client, ILoggerFactory loggerFactory) :
                                                     .CreateLogger<ChatService>();
     
     /// <inheritdoc/>
-    public async IAsyncEnumerable<ChatResponse> SendMessageAsync(string message)
+    public async IAsyncEnumerable<ChatResponse> SendMessageAsync(List<ChatMessage> messages)
     {
-        if (string.IsNullOrWhiteSpace(message))
+        if (messages == null || messages.Count == 0)
         {
-            _logger.LogWarning("빈 메시지가 전송되었습니다.");
+            _logger.LogWarning("빈 메시지 목록이 전송되었습니다.");
path: src/InterviewAssistant.Web/Services/ChatService.cs
user: login: choi-sunggyu
body: 빈 메세지 목록은 생기는 케이스는 있을 수 없다고 생각했을 때는 error가 맞다고 생각했었는데

빈 메세지 목록이 만약 생겼더라도 시스템에 치명적인 문제를 일으키지는 않는다고 생각하고 yield break 조치가 취해져서 warning이 적절하다고 생각합니다
created_at: 2025-04-29T08:00:29Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-04-27T16:06:05Z
body: ## Test Results
20 tests   20 ✅  15s ⏱️
 8 suites   0 💤
 4 files     0 ❌

Results for commit 9667807b.

[test-results]:data:application/gzip;base64,H4sIAAKQEGgC/12Myw6DIBBFf8Ww7gKsIO3PNMBAMqlKg7Ay/feO9KF2d8+5yVlYwMHP7Np0p4bNBXMFTQAlmYxxIhSSmJ68fi3/wm0uzv2ZOz7IbCIYHA7CpxTTx6Qy/YrrPgTfYutV3uUq72sujiNmAnZRqte8txygtQr82XknPARhhNSgLFjdGWk5e74AydezogABAAA=

:recycle: This comment has been updated with latest results.
author_association: NONE
user: login: parknew0
created_at: 2025-04-28T15:15:32Z
body: 현재는 ChatService가 string 하나만 받아서 단일 입력만 보내고 있습니다. 
이를 전체 대화 히스토리(List<ChatMessage>)를 보내는 방식으로 바꾸기 위해서는 크게,
1. ChatService.cs에서는 SendMessageAsync(string message) 메서드를 SendMessageAsync(List<ChatMessage> messages)로 수정하고, ChatRequest 생성 시 전체 messages를 담도록 변경하고,

2. Home.razor에서는 ChatService.SendMessageAsync(currentInput) 호출부를 
ChatService.SendMessageAsync(messages)로 수정하면 될 것 같습니다.

총 2개 파일(ChatService.cs, Home.razor)만 수정하면 적용 가능하지 않을까 싶습니다!!
author_association: MEMBER
user: login: justinyoo
created_at: 2025-04-29T00:24:23Z
body: 동의합니다!
author_association: CONTRIBUTOR