DOCS/journey/troubleshooting/251123_frontend_message_duplicate_display_fix.md

5.6 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 불일치로 인한 중복 표시

  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_messagerobeing_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)handleSendsetMessages(prev => [...prev, ...]) 비동기 경쟁 (핵심 원인):

  • loadInitialMessagessetMessages(sortedMessages)는 전체 덮어쓰기
  • handleSendsetMessages(prev => [...prev, robeingMessage])는 추가
  • 두 비동기 작업이 동시에 실행되면:
    • 히스토리 로드가 먼저 끝나면 setMessages(sortedMessages)로 덮어쓰기
    • 이후 handleSendsetMessages(prev => [...prev, robeingMessage])가 실행되면
    • 히스토리에 이미 포함된 메시지와 동일한 내용이 한 번 더 추가됨
    • 결과: 같은 텍스트, 다른 ID → 두 번 표시됨

해결 방안

적용 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:272

수정 전:

setMessages(sortedMessages);  // 전체 덮어쓰기

수정 후:

// 기존 메시지와 병합하면서 중복 제거 (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)의 전체 덮어쓰기 문제 해결
  • handleSendsetMessages(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 (대화 조회)

문서 끝