6.5 KiB
6.5 KiB
프론트엔드 메시지 중복 표시 문제 해결
작성일: 2025-11-23
작성자: admin
관련 서비스: frontend-customer, rb8001
상태: ✅ 해결 완료
문제 상황
증상
- 사용자가 메시지 전송 시 RoBeing 응답이 두 번 표시됨
- 동일한 응답 메시지가 연속으로 나타남
- 예시:
11/23 오전 12:08 김종태: hi 11/23 오전 12:08 RoBeing: 안녕하세요! 무엇을 도와드릴까요? 😊 11/23 오전 12:08 RoBeing: 안녕하세요! 무엇을 도와드릴까요? 😊 ← 중복
원인 분석
핵심 문제: 메시지 ID 불일치로 인한 중복 표시
-
새 메시지 전송 시 (
chat-interface.tsx:456-464):- API 응답 받음 →
handleSend에서 메시지 추가 - ID 생성:
id: (Date.now() + 1).toString()(예:"1732300800001") - DB 저장:
save_message_conversation호출 (main.py:110-115)
- API 응답 받음 →
-
히스토리 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")
- DB에서 대화를 가져와
-
중복 발생 시나리오:
사용자: "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. loadInitialMessages의 두 경로가 모두 setMessages 호출 (핵심 원인):
- 경로 1:
getMessages()로 히스토리 로드 → 병합 로직 실행 후return - 경로 2:
getMessages()가 빈 배열 반환 →getUserHistory()로 기본 메시지 생성 →setMessages(initialMessages)실행 (덮어쓰기) - 문제:
getMessages()가 빈 배열을 반환하면return이 실행되지 않아 기본 메시지가 추가됨 setMessages(initialMessages)는 덮어쓰기 방식이라, 이미 추가된 메시지와 병합되지 않음- 결과:
handleSend에서 추가한 메시지와 기본 메시지가 중복 표시됨
해결 방안
적용 1: useEffect 의존성에서 user 제거
위치: frontend-customer/src/components/chat-interface.tsx:378
수정 전:
}, [isAuthenticated, user]); // config와 historyLoaded 제거 - 무한 루프 방지
수정 후:
}, [isAuthenticated]); // user 제거 - 히스토리 중복 로드 방지
효과:
user변경 시loadInitialMessages가 재실행되지 않음- 히스토리는 로그인 성공 시(
isAuthenticated변경) 1번만 로드
적용 2: 히스토리 로드 실패 시 기본 메시지 추가 방지
위치: frontend-customer/src/components/chat-interface.tsx:250-306
수정 내용:
getMessages()가 빈 배열을 반환해도, 이미 메시지가 있으면 기본 메시지를 추가하지 않도록 수정historyLoadedSuccessfully플래그 추가하여 히스토리 로드 성공 여부 추적- 히스토리 로드 실패 시에도 이미 메시지가 있으면
return하여 기본 메시지 추가 방지
효과:
getMessages()가 빈 배열을 반환해도 불필요한 기본 메시지 추가 방지- 이미 메시지가 있는 상태에서
loadInitialMessages가 재실행되어도 중복 방지
적용 3: 기본 메시지 병합 방식으로 변경
위치: frontend-customer/src/components/chat-interface.tsx:394-422
수정 전:
setMessages(initialMessages); // 덮어쓰기
수정 후:
// initialMessages가 있을 때만 병합 방식으로 추가 (덮어쓰기 방지)
if (initialMessages.length > 0) {
setMessages(prev => {
// 기존 메시지가 있으면 병합, 없으면 새로 설정
if (prev.length === 0) {
return initialMessages;
}
// 기존 메시지와 병합하면서 중복 제거
const existingKeys = new Set(
prev.map(m => `${m.timestamp.getTime()}_${m.sender}_${m.text}`)
);
const newMessages = initialMessages.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(initialMessages)의 덮어쓰기 문제 해결handleSend에서 추가한 메시지와 기본 메시지를 병합하여 중복 방지- timestamp + sender + text 기준으로 중복 체크하여 같은 메시지가 두 번 추가되지 않음
검증
테스트 시나리오
- 로그인 후 메시지 전송
- 응답이 한 번만 표시되는지 확인
- 페이지 새로고침 후 히스토리가 정상 로드되는지 확인
교훈
- useEffect 의존성 최소화: 필요한 경우에만 의존성 배열에 포함
- 히스토리 로드 타이밍: 로그인 완료 시 한 번만 로드하는 것이 일반적
- 메시지 ID 통일: 향후 백엔드
message_id를 프론트엔드에서 활용하여 ID 기반 중복 체크 고려
관련 파일
frontend-customer/src/components/chat-interface.tsx(수정됨)rb8001/main.py(히스토리 API)rb8001/app/state/database.py(대화 조회)
문서 끝