docs: 프론트엔드 메시지 중복 표시 문제 해결 문서 업데이트
This commit is contained in:
parent
5f28de18fa
commit
725147f58b
@ -0,0 +1,165 @@
|
||||
# 프론트엔드 메시지 중복 표시 문제 해결
|
||||
|
||||
## 작성일: 2025-11-23
|
||||
## 작성자: admin
|
||||
## 관련 서비스: frontend-customer, rb8001
|
||||
## 상태: ✅ 해결 완료
|
||||
|
||||
---
|
||||
|
||||
## 문제 상황
|
||||
|
||||
### 증상
|
||||
- 사용자가 메시지 전송 시 RoBeing 응답이 **두 번** 표시됨
|
||||
- 동일한 응답 메시지가 연속으로 나타남
|
||||
- 예시:
|
||||
```
|
||||
11/23 오전 12:08 김종태: hi
|
||||
11/23 오전 12:08 RoBeing: 안녕하세요! 무엇을 도와드릴까요? 😊
|
||||
11/23 오전 12:08 RoBeing: 안녕하세요! 무엇을 도와드릴까요? 😊 ← 중복
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 원인 분석
|
||||
|
||||
### 핵심 문제: 메시지 ID 불일치로 인한 중복 표시
|
||||
|
||||
1. **새 메시지 전송 시** (`chat-interface.tsx:456-464`):
|
||||
- API 응답 받음 → `handleSend`에서 메시지 추가
|
||||
- ID 생성: `id: (Date.now() + 1).toString()` (예: `"1732300800001"`)
|
||||
- DB 저장: `save_message_conversation` 호출 (main.py:110-115)
|
||||
|
||||
2. **히스토리 API 응답 구조** (`rb8001/main.py:717-744`):
|
||||
- DB에서 대화를 가져와 `user_message`와 `robeing_response`로 분리
|
||||
- 각각 별도 메시지로 반환:
|
||||
- `id: f"{msg_id}_user"` (예: `"123_user"`)
|
||||
- `id: f"{msg_id}_robeing"` (예: `"123_robeing"`)
|
||||
|
||||
3. **중복 발생 시나리오**:
|
||||
```
|
||||
사용자: "hi" 전송
|
||||
↓
|
||||
handleSend 실행:
|
||||
- API 호출 → 응답 받음
|
||||
- DB에 저장 (conversation_log_id = 123)
|
||||
- 프론트엔드에 메시지 추가: id = "1732300800001", text = "안녕하세요! 무엇을 도와드릴까요? 😊"
|
||||
↓
|
||||
(동시 또는 이후)
|
||||
loadInitialMessages useEffect 재실행:
|
||||
- user 객체 변경 감지
|
||||
- /api/history 호출
|
||||
- DB에서 방금 저장된 대화 조회 (id=123)
|
||||
- 히스토리 응답: id = "123_robeing", text = "안녕하세요! 무엇을 도와드릴까요? 😊"
|
||||
↓
|
||||
결과: 같은 텍스트, 다른 ID → 두 번 표시됨
|
||||
```
|
||||
|
||||
### 근본 원인
|
||||
|
||||
**1. `loadInitialMessages` useEffect가 `user` 변경 시마다 재실행** (1차 원인, 해결됨):
|
||||
- 의존성 배열: `[isAuthenticated, user]` (line 378)
|
||||
- `user` 객체는 참조가 변경될 수 있어 불필요한 재실행 발생
|
||||
- 히스토리는 "로그인 성공 후 1번만" 불러오면 충분한데, `user`를 의존성에 넣어서 "유저 객체 변경 시마다" 다시 불러오는 것이 과함
|
||||
|
||||
**2. `setMessages(sortedMessages)`와 `handleSend`의 `setMessages(prev => [...prev, ...])` 비동기 경쟁** (핵심 원인):
|
||||
- `loadInitialMessages`의 `setMessages(sortedMessages)`는 전체 덮어쓰기
|
||||
- `handleSend`의 `setMessages(prev => [...prev, robeingMessage])`는 추가
|
||||
- 두 비동기 작업이 동시에 실행되면:
|
||||
- 히스토리 로드가 먼저 끝나면 `setMessages(sortedMessages)`로 덮어쓰기
|
||||
- 이후 `handleSend`의 `setMessages(prev => [...prev, robeingMessage])`가 실행되면
|
||||
- 히스토리에 이미 포함된 메시지와 동일한 내용이 한 번 더 추가됨
|
||||
- 결과: 같은 텍스트, 다른 ID → 두 번 표시됨
|
||||
|
||||
---
|
||||
|
||||
## 해결 방안
|
||||
|
||||
### 적용 1: useEffect 의존성에서 `user` 제거
|
||||
|
||||
**위치**: `frontend-customer/src/components/chat-interface.tsx:378`
|
||||
|
||||
**수정 전**:
|
||||
```typescript
|
||||
}, [isAuthenticated, user]); // config와 historyLoaded 제거 - 무한 루프 방지
|
||||
```
|
||||
|
||||
**수정 후**:
|
||||
```typescript
|
||||
}, [isAuthenticated]); // user 제거 - 히스토리 중복 로드 방지
|
||||
```
|
||||
|
||||
**효과**:
|
||||
- `user` 변경 시 `loadInitialMessages`가 재실행되지 않음
|
||||
- 히스토리는 로그인 성공 시(`isAuthenticated` 변경) 1번만 로드
|
||||
|
||||
### 적용 2: 히스토리 메시지 병합 및 중복 제거
|
||||
|
||||
**위치**: `frontend-customer/src/components/chat-interface.tsx:272`
|
||||
|
||||
**수정 전**:
|
||||
```typescript
|
||||
setMessages(sortedMessages); // 전체 덮어쓰기
|
||||
```
|
||||
|
||||
**수정 후**:
|
||||
```typescript
|
||||
// 기존 메시지와 병합하면서 중복 제거 (timestamp + sender + text 기준)
|
||||
setMessages(prev => {
|
||||
const existingKeys = new Set(
|
||||
prev.map(m => `${m.timestamp.getTime()}_${m.sender}_${m.text}`)
|
||||
);
|
||||
const newMessages = sortedMessages.filter(msg => {
|
||||
const msgTimestamp = msg.timestamp instanceof Date
|
||||
? msg.timestamp.getTime()
|
||||
: new Date(msg.timestamp).getTime();
|
||||
const key = `${msgTimestamp}_${msg.sender}_${msg.text}`;
|
||||
return !existingKeys.has(key);
|
||||
});
|
||||
// 기존 메시지와 새 메시지를 시간순으로 병합
|
||||
const merged = [...prev, ...newMessages].sort((a, b) => {
|
||||
const timeA = a.timestamp.getTime();
|
||||
const timeB = b.timestamp.getTime();
|
||||
if (timeA === timeB) {
|
||||
return a.sender === 'user' ? -1 : 1;
|
||||
}
|
||||
return timeA - timeB;
|
||||
});
|
||||
return merged;
|
||||
});
|
||||
```
|
||||
|
||||
**효과**:
|
||||
- `setMessages(sortedMessages)`의 전체 덮어쓰기 문제 해결
|
||||
- `handleSend`의 `setMessages(prev => [...prev, robeingMessage])`와 비동기 경쟁 상황에서도 중복 방지
|
||||
- timestamp + sender + text 기준으로 중복 체크하여 같은 메시지가 두 번 추가되지 않음
|
||||
- 기존 메시지와 히스토리 메시지를 시간순으로 병합하여 정상 표시
|
||||
|
||||
---
|
||||
|
||||
## 검증
|
||||
|
||||
### 테스트 시나리오
|
||||
1. 로그인 후 메시지 전송
|
||||
2. 응답이 한 번만 표시되는지 확인
|
||||
3. 페이지 새로고침 후 히스토리가 정상 로드되는지 확인
|
||||
|
||||
---
|
||||
|
||||
## 교훈
|
||||
|
||||
1. **useEffect 의존성 최소화**: 필요한 경우에만 의존성 배열에 포함
|
||||
2. **히스토리 로드 타이밍**: 로그인 완료 시 한 번만 로드하는 것이 일반적
|
||||
3. **메시지 ID 통일**: 향후 백엔드 `message_id`를 프론트엔드에서 활용하여 ID 기반 중복 체크 고려
|
||||
|
||||
---
|
||||
|
||||
## 관련 파일
|
||||
- `frontend-customer/src/components/chat-interface.tsx` (수정됨)
|
||||
- `rb8001/main.py` (히스토리 API)
|
||||
- `rb8001/app/state/database.py` (대화 조회)
|
||||
|
||||
---
|
||||
|
||||
**문서 끝**
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user