src/app/chatbot/[userId]/page.tsx는 로컬 상태(messages)만으로 챗 UI를 구성하고 있어, 새로고침 시 기록이 사라지고 세션 ID 개념이 없음.src/apis/aiApi.ts의askChatAPIV2는 질문/카테고리/페르소나만 전송하며, SSE 이벤트도 검색 관련 정보만 처리한다.session,session_saved,session_error등 새 이벤트와 REST 세션 API가 빠져 있음.- 세션/히스토리 REST 요청을 위한 API 클라이언트(
GET /ai/v2/sessions,/sessions/:id/messages) 및 타입이 존재하지 않으며, UI에서도 세션 목록/관리 탭이 없다.
- ASK 요청 시 세션 ID를
null로 넘겨 신규 세션을 만들고, SSE에서 받은session_id를 이후 질문에 재사용. - 내 세션 리스트를 불러와 좌측 “세션 관리” 탭(또는 패널)로 보여주고, 커서 기반 무한 로딩을 지원.
- 특정 세션을 선택하면 해당 세션의 메시지를 최신 순으로 불러오고, 스크롤 최상단 진입 시 과거 메시지를 추가 로딩(무한 스크롤).
- SSE 스트림으로 들어오는 메시지를 현재 세션의 UI에 반영하고,
session_saved,session_error에 따라 상태 및 토스트를 갱신.
src/apis/aiApi.tsaskChatAPIV2(question, userId, sessionId, categoryId, personaId, handlers, options)처럼 시그니처를 바꿔sessionId를 명시적으로 받는다(새 세션일 땐null).requester_user_id는 서버에서 세션 생성 시 검증하므로 body에 항상 넣는다.- SSE 파서에
session,session_saved,session_error,session_timeout등을 추가하고, 핸들러 타입에onSession,onSessionSaved,onSessionError콜백을 포함한다.session-id헤더는 fallback 으로만 쓰고, 기본은session이벤트 payload를 신뢰하도록 명시. - Context/answer 이외의 메시지(
event: message_user,event: message_assistant등) 발생 가능성에 대비해 공통onStreamMessage(role, chunk)시그니처를 추가하고, 추후 히스토리 append 로직이 같은 포맷을 쓰도록 한다.
- 세션 REST API 클라이언트
src/apis/aiSessionApi.ts파일을 새로 만들고getChatSessions,getChatSession,getChatSessionMessagesAPI를 정의한다. 파일 내부에서aiFetch를 재사용해 베이스 URL/에러 처리를 일관되게 유지한다.src/utils/types.ts에ChatSession,ChatSessionMessage,ChatSessionPaging타입을 추가해 API/스토어/UI가 동일한 구조를 바라보도록 한다(예:ChatSessionMessage['role']는'user' | 'assistant').
- 공통 zustand 스토어
src/store/ChatSessionStore.ts를 만들고,sessions,sessionsPaging,currentSessionId,messagesBySession,messagesPagingBySession,isSessionPanelOpen,isStreaming등을 한 곳에서 관리한다.- 주요 액션:
fetchInitialSessions,fetchMoreSessions,selectSession(sessionId | null),resetSession(userId),fetchMessages(sessionId, { cursor, direction }),prependMessages,appendMessages,upsertSessionFromStream,appendStreamingChunk,setPanelOpen. - SSE 이벤트 핸들러는 이 스토어 액션을 직접 호출해 전역 상태를 갱신하고, 페이지 컴포넌트는
useChatSessionStore(selector)조합으로 필요한 조각만 구독한다.
- 훅 레이어
useChatSessions(userId)훅은 zustand 액션을 감싼 얇은 커스텀 훅으로, 초기 로딩/추가 로딩 호출과 MEMOized 파생값(sortedSessions,selectedSession)을 제공한다.useSessionMessages(sessionId)훅 역시 zustand 상태를 구독하면서 스크롤 로딩/스트리밍 append 를 담당한다. UI가 훅만 사용하도록 만들어 컴포넌트 복잡도를 낮춘다.
- 레이아웃
- 챗봇 페이지를 좌측
세션 패널+ 우측대화 영역으로 나누는 컨테이너 컴포넌트 생성. - 상단 햄버거 버튼으로 세션 패널을 열고 닫을 수 있도록 제어 상태(
isSessionPanelOpen)를 두고, 모바일/태블릿에서는 전체 화면 Drawer(뒤 배경 dim 처리), 데스크톱에서는 기본 오픈 + width 축소 애니메이션을 적용한다. 햄버거 버튼은ProfileHeader우측에 배치해 항상 접근 가능하게 한다.
- 챗봇 페이지를 좌측
- 세션 패널
- 세션 카드에는
title(없으면 첫 user 메시지 앞부분),updated_at,message_count를 표시하고 선택 상태를 강조. - “새 세션 시작” 버튼: 클릭 시 현재
currentSessionId를null로 초기화하고 다음 질문을 새 세션으로 전송하도록 상태 변경. - 하단 “더 보기” 또는 스크롤 감지 시
loadMore()호출.
- 세션 카드에는
- 메시지 영역
ChatMessages를 그대로 쓰되 prop 구조를messages: Array<UIChatMessage | ChatSessionMessage & { inspector?: InspectorData }>로 확장하고, 서버 메시지에는inspector가 없을 수 있음을 허용한다(4단계에서 관련 호출부 전체를 한 번에 업데이트할 예정).- 스크롤 영역 상단에
divsentinel을 두고IntersectionObserver로 진입 감지 →prependOlder()호출, 응답 도착 후에는 기존 scrollHeight 차이를 이용해 스크롤 위치를 유지(React 18에서도 안정적). loading 중에는 skeleton bubble을 잠시 노출. - Inspector 패널은 스트리밍 메시지(실시간 질문)에만 붙이고, 과거 히스토리는 collapse 된
ContextSummary정도만 노출하는 식으로 경량화한다.
handleSubmit- zustand에서
currentSessionId를 읽어askChatAPIV2에 전달하고,null일 경우 body에session_id: null,user_id: ownerUserId,requester_user_id: viewerId를 명시한다.viewerId는useAuthStore의userId값(로그인 사용자)이며, 비로그인일 때는 추후 정의할 게스트 ID(or null) 정책을 따른다. - SSE
onSession콜백이 오면 즉시selectSession(newId)+upsertSessionFromStream(payload)를 호출해 패널 리스트에도 새 항목을 넣는다. 헤더 값만 들어오고 이벤트가 없을 경우 대비하여 fallback 로직도 추가. - 전송 직후엔 로컬 메시지를
messagesBySession[tempSessionKey]에 push 해 UI가 즉시 반응하도록 하고, SSE chunk 가 오면appendStreamingChunk액션이 현재 세션 메시지를 업데이트한다.
- zustand에서
- 스트림 종료 처리
session_saved이벤트에서cached여부와 최종message_count를 받아 해당 세션 카드 메타 정보를 갱신하고, 필요하면fetchMessages(sessionId, { direction: 'forward' })로 최신화한다.session_error수신 시 현재 스트림 메시지를 에러 상태로 표시하고,selectSession(null)+ 토스트 노출을 한 번에 처리한다(실패 세션은 리스트에서 제거하거나failed구분값을 두어 재시도 UX 제공).
- 메시지 히스토리 로딩 동작 정의
- 최초 세션 선택 시
direction=backward로 20개 로드 후 리스트 하단으로 스크롤, 이후 새 메시지가 오면 append. - 상단 sentinel 이 viewport 에 들어오면
cursor=messagesPagingBySession[sessionId].prevCursor로 과거 데이터를 불러와prependMessages한다. prepend 직후(scrollHeight_after - scrollHeight_before)만큼scrollTop을 보정해 위치가 튀지 않도록 한다.
- 최초 세션 선택 시
- 에러/빈 상태 처리
- 세션이 없을 때는 “첫 질문을 해보세요” CTA 제공.
- 목록/메시지 로딩이 실패하면 zustand 에
lastError를 기록하고, 패널/메시지 영역 각각에 재시도 버튼을 노출한다. 에러 상태가 풀리면 자동으로 토스트를 닫는다.
- API 클라이언트/타입 작성 → 유닛(또는 간단한 런타임 테스트)으로 JSON 형태 검증.
- zustand 스토어 + 훅 구현 → Fake API 응답(Mock Service Worker 또는 임시 JSON)으로 세션 리스트/메시지 로직을 검증.
- UI/흐름 통합 → 실제 SSE 없이도 dev 에서 동작하도록
askChatAPIV2를 주입형으로 감싸고, 필요 시ChatbotPage.stories.tsx같은 문서화도 고려.
- [feat(api)] ai 세션 타입/클라이언트 추가
src/utils/types.ts에ChatSession*타입 정의.src/apis/aiSessionApi.ts신설, 세션 목록/상세/메시지 API 함수 구현.askChatAPIV2시그니처 초안만 정의(아직 호출부 수정 X) 및 새로운 SSE 이벤트 핸들러 타입 선언.
- [feat(store)] ChatSessionStore 및 훅 도입
src/store/ChatSessionStore.ts생성, 상태/액션/초기값 구현.src/hooks/useChatSessions.ts,src/hooks/useSessionMessages.ts작성해 zustand 액션을 래핑.- 임시 mock 함수나 dev-only util로 스토어 로직을 간단히 검증.
- [feat/ui] 세션 패널/햄버거 레이아웃 구축
src/app/chatbot/[userId]/page.tsx레이아웃을 세션 패널 + 대화 영역으로 분리.- 햄버거 버튼, Drawer/축소 UI, 세션 리스트 컴포넌트(
SessionListPanel.tsx) 추가. - 패널과 store를 연결, 기본 세션 로딩/빈 상태 UI 구현.
- [feat(chat)] 메시지 영역 무한 스크롤 + SSE 연동
ChatMessages컴포넌트 prop 확장 및 sentinel 기반 무한 스크롤 구현.askChatAPIV2본격 수정:session_id전달,onSession/onSessionSaved/onSessionError등 호출 시 zustand 액션 연동. 해당 커밋에서 기존 호출부 전부(챗봇 페이지, 기타 컴포넌트)를 새 시그니처로 업데이트.handleSubmit로직을 훅/store 중심으로 재작성하고 임시 메시지/스트리밍 업데이트 처리.
- [feat/polish] 오류 처리·토스트·UX 마감
session_saved캐시 배지,session_error토스트, 재시도 버튼, loading skeleton 등 UX 디테일 반영.- API 실패/네트워크 해제 대응, 패널/메시지 에러 상태 컴포넌트.
- 문서(TASK.md or ASK_SESSION_INTEGRATION.md subsection) 및 TODO 코멘트 정리, 린트 확인.