-
Notifications
You must be signed in to change notification settings - Fork 0
채팅방 목록 컴포넌트 추가 및 mock 데이터 추가 #294
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
🚀 storybook url: https://689bfce92a7bd08f5aa1edbe-qicuxjsvlx.chromatic.com/ |
9a308c6 to
d9de8ef
Compare
Walkthrough이 PR은 채팅 페이지의 UI를 재구성하고 사이드 네비게이션과 우측 패널에 채팅 관련 컴포넌트를 추가합니다. ChatPage에서 HomeQueryBox, CardList, LinkCard, Tab, UserChatBox 등으로 기존 QueryBox를 분리하고, 환경변수 기반 모의데이터 토글을 도입해 mock chats, chatLinks, chatReasoning을 연결합니다. 채팅/링크 관련 전역 상태(useChatStore, useChatRightPanelStore)를 추가·연동하고, 레이아웃에 ChatRightSidebar를 삽입했으며 사이드바에 채팅방 목록 렌더러(ChatRoomList)를 추가했습니다. Possibly related PRs
Pre-merge checks and finishing touches❌ Failed checks (1 inconclusive)
✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (5)
src/components/layout/SideNavigation/components/ChatRightSidebar/ChatRightSidebar.tsx (1)
21-21: memo prop이 빈 문자열로 하드코딩되어 있습니다.현재 ChatRightPanelLink 타입에 memo 필드가 없어서 빈 문자열을 전달하는 것으로 보입니다. 향후 메모 기능이 추가될 경우 스토어 타입에 memo 필드를 추가하는 것을 고려하세요.
src/stories/SideNavigation.stories.tsx (1)
19-37: 스토리북 렌더 함수 내 useEffect 사용을 리팩토링하세요.Storybook 공식 문서에 따르면 로더는 스토리가 렌더링되기 전에 실행되며, 로더를 통해 데이터를 주입하도록 설계되었습니다. 렌더 함수 내에서 직접
useEffect를 사용하면 React에서 렌더 과정은 순수해야 하며 사이드 이펙트는 이벤트 핸들러나 Effect로 따로 분리되어야 합니다. 대신 다음 중 하나를 고려하세요:
- 데코레이터: 연결된 컴포넌트에 필요한 상태 데이터를 제공하기 위해 데코레이터를 사용하세요.
- 로더: 로더를 사용하면 모든 자산을 로드하거나 원격 API에서 데이터를 가져올 수 있습니다.
src/components/layout/SideNavigation/components/ChatRoomList/ChatRoomList.tsx (1)
4-13: 타입 내보내기 고려
ChatRoomItem타입이 다른 곳에서 재사용될 가능성이 있습니다. 필요시 외부에서 import할 수 있도록 타입을 export하는 것을 고려해 보세요.🔎 타입 내보내기 제안
-type ChatRoomItem = { +export type ChatRoomItem = { id: number; title: string; href: string; }; -type ChatRoomListProps = { +export type ChatRoomListProps = { items: ChatRoomItem[]; activeId?: number | null; };src/app/(route)/chat/ChatPage.tsx (2)
69-69:linkCards를useMemo로 감싸는 것을 권장합니다.다른 파생 데이터(
messages,chatPairs)와 일관성을 위해, 그리고 불필요한 재계산을 방지하기 위해useMemo를 사용하는 것이 좋습니다.🔎 useMemo 적용 제안
- const linkCards = activeId && useMockData ? (chatLinksById[activeId] ?? []) : []; + const linkCards = useMemo(() => { + return activeId && useMockData ? (chatLinksById[activeId] ?? []) : []; + }, [activeId, useMockData]);
94-130: 탭 콘텐츠 내LinkCard렌더링 로직 중복"답변" 탭(라인 94-109)과 "링크" 탭(라인 115-130)에서
LinkCard렌더링 로직이 거의 동일합니다. 유지보수성을 위해 공통 컴포넌트나 변수로 추출하는 것을 고려해 보세요.🔎 중복 제거 제안
// 컴포넌트 내부에 헬퍼 함수 또는 변수 추가 const renderLinkCards = (keyPrefix: string) => ( linkCards.length === 0 ? ( <p className="text-gray400 text-sm">표시할 링크가 없습니다.</p> ) : ( <CardList> {linkCards.map(link => ( <LinkCard key={`${user.id}-${keyPrefix}-${link.id}`} title={link.title} link={link.url} summary={link.summary} imageUrl={link.imageUrl ?? ''} onClick={() => setSelectedLink(link)} /> ))} </CardList> ) );
📜 Review details
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
src/app/(route)/chat/ChatPage.tsx(1 hunks)src/app/layout.tsx(2 hunks)src/components/layout/SideNavigation/SideNavigation.tsx(2 hunks)src/components/layout/SideNavigation/components/ChatRightSidebar/ChatRightSidebar.tsx(1 hunks)src/components/layout/SideNavigation/components/ChatRoomList/ChatRoomList.tsx(1 hunks)src/mocks/fixtures/chatLinks.ts(1 hunks)src/mocks/fixtures/chatMessages.ts(2 hunks)src/mocks/index.ts(1 hunks)src/stores/chatRightPanelStore.ts(1 hunks)src/stories/SideNavigation.stories.tsx(2 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-11-23T14:52:20.769Z
Learnt from: Bangdayeon
Repo: Team-SoFa/linkiving PR: 102
File: src/components/layout/SideNavigation/components/AddLinkModal/AddLinkModal.tsx:23-23
Timestamp: 2025-11-23T14:52:20.769Z
Learning: In src/components/layout/SideNavigation/components/AddLinkModal/AddLinkModal.tsx, the hardcoded '/file.svg' thumbnail is intentional as a placeholder because the backend API for fetching link metadata/thumbnails is still in preparation. The actual thumbnail fetch logic will be implemented after the backend is ready.
Applied to files:
src/components/layout/SideNavigation/components/ChatRightSidebar/ChatRightSidebar.tsxsrc/components/layout/SideNavigation/SideNavigation.tsx
📚 Learning: 2025-11-23T12:03:33.890Z
Learnt from: Bangdayeon
Repo: Team-SoFa/linkiving PR: 97
File: src/components/basics/LinkCard/LinkCard.tsx:12-19
Timestamp: 2025-11-23T12:03:33.890Z
Learning: In src/components/basics/LinkCard/LinkCard.tsx, the summary prop should remain required (string type) because the backend always provides it as a string value. The isHaveSummary flag controls whether to display the summary text or show the AddSummaryButton, not whether the data exists.
Applied to files:
src/mocks/fixtures/chatLinks.ts
🧬 Code graph analysis (5)
src/app/layout.tsx (1)
src/components/layout/SideNavigation/components/ChatRightSidebar/ChatRightSidebar.tsx (1)
ChatRightSidebar(7-27)
src/components/layout/SideNavigation/components/ChatRoomList/ChatRoomList.tsx (1)
src/stores/sideNavStore.ts (1)
useSideNavStore(9-13)
src/components/layout/SideNavigation/SideNavigation.tsx (5)
src/stores/modalStore.ts (1)
useModalStore(18-23)src/stores/sideNavStore.ts (1)
useSideNavStore(9-13)src/stores/chatStore.ts (1)
useChatStore(16-30)src/mocks/fixtures/chats.ts (1)
mockChats(10-17)src/components/layout/SideNavigation/components/ChatRoomList/ChatRoomList.tsx (1)
ChatRoomList(15-47)
src/stories/SideNavigation.stories.tsx (3)
src/stores/chatStore.ts (1)
useChatStore(16-30)src/mocks/fixtures/chats.ts (1)
mockChats(10-17)src/components/layout/SideNavigation/SideNavigation.tsx (1)
SideNavigation(16-77)
src/app/(route)/chat/ChatPage.tsx (5)
src/stores/chatRightPanelStore.ts (1)
useChatRightPanelStore(17-21)src/mocks/fixtures/chats.ts (1)
mockChats(10-17)src/mocks/fixtures/chatMessages.ts (1)
chatHistoryById(15-81)src/mocks/fixtures/chatLinks.ts (1)
chatLinksById(9-65)src/components/basics/CardList/CardList.tsx (1)
CardList(11-15)
🪛 GitHub Actions: CI
src/app/(route)/chat/ChatPage.tsx
[error] 150-150: pnpm exec tsc --noEmit: Type '{ onSubmit: () => void; }' is not assignable to type 'IntrinsicAttributes' (TS2322).
🪛 GitHub Check: ci
src/app/(route)/chat/ChatPage.tsx
[failure] 150-150:
Type '{ onSubmit: () => void; }' is not assignable to type 'IntrinsicAttributes'.
🔇 Additional comments (13)
src/stores/chatRightPanelStore.ts (1)
1-21: LGTM! 잘 구조화된 스토어 구현입니다.Zustand를 사용한 전역 상태 관리 구현이 명확하고 타입 안전합니다. 선택된 링크를 관리하는 로직이 간결하며, 예상대로 동작합니다.
src/mocks/index.ts (1)
6-6: LGTM!chatLinks 픽스처를 mocks API에 추가하는 표준적인 export 패턴입니다.
src/app/layout.tsx (1)
4-4: LGTM! 레이아웃 통합이 적절합니다.ChatRightSidebar를 루트 레이아웃에 추가했습니다. 컴포넌트 내부에서
/chat라우트 체크를 통해 조건부 렌더링을 처리하고 있어 예상대로 동작합니다.Also applies to: 64-64
src/components/layout/SideNavigation/SideNavigation.tsx (3)
3-4: LGTM! 스토어 통합이 올바릅니다.useChatStore를 통해 채팅방 목록과 활성 채팅 ID를 가져오는 로직이 명확합니다. 모의 데이터 토글 환경 변수도 적절하게 사용되었습니다.
Also applies to: 19-20
28-33: 채팅방 목록 변환 로직이 정확합니다.chats 데이터를 ChatRoomList에 필요한 형식으로 변환하는 로직이 명확하고 올바릅니다. 모의 데이터 폴백 처리도 적절합니다.
71-71: LGTM! ChatRoomList 통합이 완료되었습니다.chatRooms와 activeChatId를 ChatRoomList에 전달하여 채팅방 목록 UI를 렌더링하는 로직이 올바릅니다.
src/mocks/fixtures/chatMessages.ts (1)
29-30: LGTM! 모의 데이터 확장이 적절합니다.채팅 메시지 모의 데이터에 더 풍부한 대화 내용을 추가했습니다. 테스트 및 개발 목적으로 충분한 데이터를 제공합니다.
Also applies to: 32-46, 77-78
src/components/layout/SideNavigation/components/ChatRightSidebar/ChatRightSidebar.tsx (1)
7-27: LGTM! 조건부 렌더링이 올바릅니다.경로와 선택된 링크 여부를 체크하여 적절하게 조건부 렌더링을 구현했습니다. LinkCardDetailPanel에 전달하는 데이터 구조도 올바릅니다.
src/mocks/fixtures/chatLinks.ts (1)
1-65: LGTM! 모의 링크 데이터가 잘 구조화되어 있습니다.ChatLinkCard 타입 정의가 명확하며, chatLinksById에 제공된 모의 데이터가 채팅 기능 테스트에 적합합니다.
src/components/layout/SideNavigation/components/ChatRoomList/ChatRoomList.tsx (1)
15-47: LGTM!컴포넌트 구현이 깔끔합니다. 시맨틱 HTML(
section,ul,li) 사용, 빈 상태 처리, 그리고useSideNavStore를 통한 조건부 렌더링이 잘 구현되어 있습니다.src/app/(route)/chat/ChatPage.tsx (3)
42-68: LGTM!
chatPairs로직이 연속된 사용자 메시지, 마지막 응답 없는 사용자 메시지 등 엣지 케이스를 적절히 처리하고 있습니다.
21-26: LGTM!URL 파라미터 파싱과 목 데이터 폴백 로직이 올바르게 구현되어 있습니다.
Number.isNaN사용과 optional chaining으로 안전하게 처리됩니다.
28-36: LGTM!
useEffect훅들이 적절한 의존성 배열과 함께 올바르게 구현되어 있습니다.
|
🚀 storybook url: https://689bfce92a7bd08f5aa1edbe-abbsgptanu.chromatic.com/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
src/app/(route)/chat/ChatPage.tsx (1)
150-150: 이전 리뷰 이슈가 해결되었습니다.과거 리뷰에서
HomeQueryBox에onSubmitprop을 전달할 수 없다는 지적이 있었으나, 현재 코드에서는 Line 150이<HomeQueryBox />로 props 없이 렌더링되어 문제가 해결되었습니다.
🧹 Nitpick comments (2)
src/app/(route)/chat/ChatPage.tsx (2)
42-68: 채팅 페어링 로직을 함수형으로 단순화할 수 있습니다.현재 명령형 스타일로 구현된
chatPairs로직이 정확하게 동작하지만,reduce를 사용하면 더 간결하고 함수형으로 작성할 수 있습니다.🔎 함수형 스타일로 리팩토링 제안
- const chatPairs = useMemo(() => { - const pairs: { - user: (typeof messages)[number]; - assistant?: (typeof messages)[number]; - }[] = []; - let pendingUser: (typeof messages)[number] | null = null; - - messages.forEach(message => { - if (message.role === 'user') { - if (pendingUser) { - pairs.push({ user: pendingUser }); - } - pendingUser = message; - } else if (message.role === 'assistant') { - if (pendingUser) { - pairs.push({ user: pendingUser, assistant: message }); - pendingUser = null; - } - } - }); - - if (pendingUser) { - pairs.push({ user: pendingUser }); - } - - return pairs; - }, [messages]); + const chatPairs = useMemo(() => { + const { pairs, pendingUser } = messages.reduce( + (acc, message) => { + if (message.role === 'user') { + if (acc.pendingUser) { + acc.pairs.push({ user: acc.pendingUser }); + } + return { ...acc, pendingUser: message }; + } else if (message.role === 'assistant' && acc.pendingUser) { + acc.pairs.push({ user: acc.pendingUser, assistant: message }); + return { ...acc, pendingUser: null }; + } + return acc; + }, + { pairs: [] as Array<{ user: typeof messages[number]; assistant?: typeof messages[number] }>, pendingUser: null as typeof messages[number] | null } + ); + if (pendingUser) pairs.push({ user: pendingUser }); + return pairs; + }, [messages]);
98-106: 중복된 LinkCard 렌더링 로직을 추출할 수 있습니다.'답변' 탭과 '링크' 탭에서 동일한 LinkCard 매핑 로직이 반복됩니다. 별도 컴포넌트나 헬퍼 함수로 추출하면 유지보수성이 향상됩니다.
🔎 중복 제거 예시
컴포넌트 상단에 헬퍼 함수 추가:
const renderLinkCards = (linkCards: typeof linkCards, keyPrefix: string) => { if (linkCards.length === 0) { return <p className="text-gray400 text-sm">표시할 링크가 없습니다.</p>; } return ( <CardList> {linkCards.map(link => ( <LinkCard key={`${keyPrefix}-${link.id}`} title={link.title} link={link.url} summary={link.summary} imageUrl={link.imageUrl ?? ''} onClick={() => setSelectedLink(link)} /> ))} </CardList> ); };그런 다음 탭 content에서 호출:
답변: ( <div className="space-y-6"> <div className="text-gray900 space-y-4 text-sm leading-[160%]"> <p>{assistant?.content ?? '응답을 생성 중입니다.'}</p> </div> <div>{renderLinkCards(linkCards, user.id)}</div> </div> ), 링크: <div>{renderLinkCards(linkCards, `${user.id}-link`)}</div>,Also applies to: 119-127
📜 Review details
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
src/app/(route)/chat/ChatPage.tsx(1 hunks)src/app/layout.tsx(2 hunks)src/components/layout/SideNavigation/SideNavigation.tsx(2 hunks)src/components/layout/SideNavigation/components/ChatRightSidebar/ChatRightSidebar.tsx(1 hunks)src/components/layout/SideNavigation/components/ChatRoomList/ChatRoomList.tsx(1 hunks)src/mocks/fixtures/chatLinks.ts(1 hunks)src/mocks/fixtures/chatMessages.ts(2 hunks)src/mocks/index.ts(1 hunks)src/stores/chatRightPanelStore.ts(1 hunks)src/stories/SideNavigation.stories.tsx(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
- src/mocks/index.ts
- src/app/layout.tsx
- src/stories/SideNavigation.stories.tsx
- src/components/layout/SideNavigation/components/ChatRoomList/ChatRoomList.tsx
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-11-23T14:52:20.769Z
Learnt from: Bangdayeon
Repo: Team-SoFa/linkiving PR: 102
File: src/components/layout/SideNavigation/components/AddLinkModal/AddLinkModal.tsx:23-23
Timestamp: 2025-11-23T14:52:20.769Z
Learning: In src/components/layout/SideNavigation/components/AddLinkModal/AddLinkModal.tsx, the hardcoded '/file.svg' thumbnail is intentional as a placeholder because the backend API for fetching link metadata/thumbnails is still in preparation. The actual thumbnail fetch logic will be implemented after the backend is ready.
Applied to files:
src/components/layout/SideNavigation/SideNavigation.tsxsrc/components/layout/SideNavigation/components/ChatRightSidebar/ChatRightSidebar.tsx
📚 Learning: 2025-11-23T12:03:33.890Z
Learnt from: Bangdayeon
Repo: Team-SoFa/linkiving PR: 97
File: src/components/basics/LinkCard/LinkCard.tsx:12-19
Timestamp: 2025-11-23T12:03:33.890Z
Learning: In src/components/basics/LinkCard/LinkCard.tsx, the summary prop should remain required (string type) because the backend always provides it as a string value. The isHaveSummary flag controls whether to display the summary text or show the AddSummaryButton, not whether the data exists.
Applied to files:
src/mocks/fixtures/chatLinks.ts
🧬 Code graph analysis (2)
src/components/layout/SideNavigation/SideNavigation.tsx (5)
src/stores/modalStore.ts (1)
useModalStore(18-23)src/stores/sideNavStore.ts (1)
useSideNavStore(9-13)src/stores/chatStore.ts (1)
useChatStore(16-30)src/mocks/fixtures/chats.ts (1)
mockChats(10-17)src/components/layout/SideNavigation/components/ChatRoomList/ChatRoomList.tsx (1)
ChatRoomList(15-47)
src/components/layout/SideNavigation/components/ChatRightSidebar/ChatRightSidebar.tsx (1)
src/stores/chatRightPanelStore.ts (1)
useChatRightPanelStore(17-21)
🔇 Additional comments (7)
src/stores/chatRightPanelStore.ts (1)
1-21: LGTM! 깔끔한 상태 관리 구현입니다.Zustand를 사용한 우측 패널 상태 관리가 명확하고 간결하게 구현되었습니다. 타입 정의와 핸들러가 직관적이며, 다른 컴포넌트에서 사용하기 적합합니다.
src/mocks/fixtures/chatLinks.ts (1)
1-65: LGTM! 잘 구조화된 모의 데이터입니다.
ChatLinkCard타입이 명확하게 정의되어 있고, 모든 모의 데이터가 필수 필드를 포함하고 있습니다.summary필드가 항상 문자열로 제공되는 점도 기존 학습 내용과 일치합니다.src/components/layout/SideNavigation/SideNavigation.tsx (2)
19-33: 채팅방 목록 통합이 잘 구현되었습니다.모의 데이터 사용 여부를 확인하고, 실제 채팅이 없을 때만 모의 데이터로 대체하는 로직이 명확합니다.
chatRooms배열을 생성하여ChatRoomList에 전달하는 구조도 적절합니다.
71-71: 채팅방 목록 렌더링이 올바르게 추가되었습니다.
ChatRoomList컴포넌트가 적절한 props(items,activeId)와 함께 렌더링되어, PR 목표인 "채팅방 목록 UI 추가"를 달성했습니다.src/components/layout/SideNavigation/components/ChatRightSidebar/ChatRightSidebar.tsx (1)
7-27: 우측 사이드바 구현이 깔끔합니다.
pathname체크와selectedLink상태 기반 조건부 렌더링이 명확하게 구현되었습니다. Line 21의 빈memoprop은 현재 플레이스홀더로 예상되며, 향후 실제 데이터 연동 시 업데이트될 것으로 보입니다.src/app/(route)/chat/ChatPage.tsx (2)
14-69: 채팅 UI 통합이 잘 구현되었습니다.URL 파라미터 기반 활성 채팅 선택, 모의 데이터 연동, 메시지 및 링크 카드 계산 로직이 명확하게 구현되었습니다.
useEffect와useMemo를 적절히 활용하여 상태 동기화와 파생 데이터 계산을 효율적으로 처리했습니다.
71-162: 채팅 레이아웃과 탭 기반 UI가 효과적으로 구현되었습니다.채팅 메시지 페어링, 탭 기반 콘텐츠 표시('답변', '링크', '단계'), 반응형 입력 박스(HomeQueryBox/UserChatBox) 전환이 잘 구현되어 PR 목표를 달성했습니다.
- add chat right sidebar and room list components - add chat mocks and tabbed AI responses
d9de8ef to
1be9781
Compare
|
🚀 storybook url: https://689bfce92a7bd08f5aa1edbe-hmeojcnsjm.chromatic.com/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (3)
src/stores/chatRightPanelStore.ts (1)
3-9: 타입 중복 고려
ChatRightPanelLink타입이src/mocks/fixtures/chatLinks.ts의ChatLinkCard타입과 동일합니다. 향후 유지보수를 위해 공통 타입을@/types디렉토리에 정의하고 재사용하는 것을 고려해 보세요.src/app/(route)/chat/ChatPage.tsx (1)
96-114: LinkCard 렌더링 로직 중복'답변' 탭과 '링크' 탭에서 동일한 LinkCard 렌더링 로직이 반복됩니다. 별도의 컴포넌트나 함수로 추출하면 유지보수가 용이해집니다.
🔎 리팩토링 예시
// ChatPage 컴포넌트 내부 또는 별도 파일로 추출 const LinkCardList = ({ links, userMessageId }: { links: typeof linkCards; userMessageId: string }) => { const { setSelectedLink } = useChatRightPanelStore(); if (links.length === 0) { return <p className="text-gray400 text-sm">표시할 링크가 없습니다.</p>; } return ( <CardList> {links.map(link => ( <LinkCard key={`${userMessageId}-${link.id}`} title={link.title} link={link.url} summary={link.summary} imageUrl={link.imageUrl ?? ''} onClick={() => setSelectedLink(link)} /> ))} </CardList> ); };src/mocks/fixtures/chatReasoning.ts (1)
6-12:linkIds와relatedLinks필드의 중복을 검토하세요.
ChatReasoningResponse타입에linkIds와relatedLinks두 필드가 모두number[]타입으로 정의되어 있습니다. 실제 데이터를 보면 모든 항목에서 이 두 필드가 항상 동일한 값을 가지고 있습니다:
- 201: linkIds: [1101, 1102], relatedLinks: [1101, 1102]
- 202: linkIds: [1201, 1202], relatedLinks: [1201, 1202]
- 203: linkIds: [1301, 1302, 1303], relatedLinks: [1301, 1302, 1303]
이러한 중복은 데이터 불일치 가능성을 높이고 유지보수를 어렵게 만들 수 있습니다. 두 필드가 서로 다른 의미를 가지는 경우가 있는지 확인하고, 그렇지 않다면 하나로 통합하는 것을 고려해보세요.
🔎 필드 통합 제안
만약 두 필드가 항상 동일한 값을 가진다면:
export type ChatReasoningResponse = { answer: string; linkIds: number[]; reasoningSteps: ChatReasoningStep[]; - relatedLinks: number[]; isFallback: boolean; };그리고 데이터에서도:
answer: '...', linkIds: [1101, 1102], reasoningSteps: [...], - relatedLinks: [1101, 1102], isFallback: false, }
📜 Review details
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (12)
src/app/(route)/chat/ChatPage.tsx(1 hunks)src/app/layout.tsx(2 hunks)src/components/layout/SideNavigation/SideNavigation.tsx(2 hunks)src/components/layout/SideNavigation/components/ChatRightSidebar/ChatRightSidebar.tsx(1 hunks)src/components/layout/SideNavigation/components/ChatRoomList/ChatRoomList.tsx(1 hunks)src/mocks/fixtures/chatLinks.ts(1 hunks)src/mocks/fixtures/chatMessages.ts(2 hunks)src/mocks/fixtures/chatReasoning.ts(1 hunks)src/mocks/index.ts(1 hunks)src/stores/chatRightPanelStore.ts(1 hunks)src/stores/linkStore.ts(1 hunks)src/stories/SideNavigation.stories.tsx(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
- src/stories/SideNavigation.stories.tsx
- src/components/layout/SideNavigation/components/ChatRightSidebar/ChatRightSidebar.tsx
- src/components/layout/SideNavigation/components/ChatRoomList/ChatRoomList.tsx
- src/mocks/index.ts
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-11-23T14:52:20.769Z
Learnt from: Bangdayeon
Repo: Team-SoFa/linkiving PR: 102
File: src/components/layout/SideNavigation/components/AddLinkModal/AddLinkModal.tsx:23-23
Timestamp: 2025-11-23T14:52:20.769Z
Learning: In src/components/layout/SideNavigation/components/AddLinkModal/AddLinkModal.tsx, the hardcoded '/file.svg' thumbnail is intentional as a placeholder because the backend API for fetching link metadata/thumbnails is still in preparation. The actual thumbnail fetch logic will be implemented after the backend is ready.
Applied to files:
src/components/layout/SideNavigation/SideNavigation.tsx
📚 Learning: 2025-11-23T12:03:33.890Z
Learnt from: Bangdayeon
Repo: Team-SoFa/linkiving PR: 97
File: src/components/basics/LinkCard/LinkCard.tsx:12-19
Timestamp: 2025-11-23T12:03:33.890Z
Learning: In src/components/basics/LinkCard/LinkCard.tsx, the summary prop should remain required (string type) because the backend always provides it as a string value. The isHaveSummary flag controls whether to display the summary text or show the AddSummaryButton, not whether the data exists.
Applied to files:
src/mocks/fixtures/chatLinks.ts
🧬 Code graph analysis (3)
src/components/layout/SideNavigation/SideNavigation.tsx (3)
src/stores/chatStore.ts (1)
useChatStore(16-30)src/mocks/fixtures/chats.ts (1)
mockChats(10-17)src/components/layout/SideNavigation/components/ChatRoomList/ChatRoomList.tsx (1)
ChatRoomList(15-47)
src/app/(route)/chat/ChatPage.tsx (8)
src/stores/chatStore.ts (1)
useChatStore(16-30)src/stores/chatRightPanelStore.ts (1)
useChatRightPanelStore(17-21)src/mocks/fixtures/chats.ts (1)
mockChats(10-17)src/mocks/fixtures/chatMessages.ts (1)
chatHistoryById(15-81)src/mocks/fixtures/chatLinks.ts (1)
chatLinksById(9-65)src/mocks/fixtures/chatReasoning.ts (1)
chatReasoningById(14-70)src/components/basics/CardList/CardList.tsx (1)
CardList(11-15)src/components/wrappers/UserChatBox/UserChatBox.tsx (1)
UserChatBox(8-22)
src/app/layout.tsx (1)
src/components/layout/SideNavigation/components/ChatRightSidebar/ChatRightSidebar.tsx (1)
ChatRightSidebar(7-27)
🔇 Additional comments (10)
src/stores/linkStore.ts (1)
12-21: LGTM!스토어에서 mock 데이터 로직을 제거하고 초기값을 빈 배열로 설정한 변경이 적절합니다. mock 데이터 처리를 컴포넌트 레벨로 이동시켜 스토어의 역할을 단순화했습니다.
src/app/layout.tsx (1)
64-64: LGTM!ChatRightSidebar를 레이아웃에 추가한 구조가 적절합니다. 컴포넌트 내부에서
/chat경로 체크를 통해 조건부 렌더링하므로 다른 페이지에서는 null을 반환합니다.src/mocks/fixtures/chatLinks.ts (1)
1-65: LGTM!타입 정의와 mock 데이터 구조가 적절합니다.
ChatLinkCard타입이 ChatRightPanelStore의ChatRightPanelLink타입과 동일한 필드를 가지고 있어 호환성이 좋습니다.src/stores/chatRightPanelStore.ts (1)
17-21: LGTM!Zustand 스토어 구현이 깔끔하고 패턴을 잘 따르고 있습니다.
src/components/layout/SideNavigation/SideNavigation.tsx (2)
28-33: LGTM!
resolvedChats로직이 스토어가 아직 초기화되지 않은 경우에도 mock 데이터로 폴백하여 안정적으로 채팅방 목록을 표시합니다.chatRooms매핑과 href 패턴도 적절합니다.
70-71: LGTM!ChatRoomList 컴포넌트가 적절한 위치에 렌더링되었습니다. PR 목표인 사이드바에 채팅방 목록 표시 기능이 잘 구현되었습니다.
src/mocks/fixtures/chatMessages.ts (1)
15-81: LGTM!채팅 메시지 mock 데이터가 적절하게 확장되었습니다. 새로운 메시지 쌍(201-3, 201-4)이 추가되어 다중 턴 대화 시나리오를 테스트할 수 있습니다.
src/app/(route)/chat/ChatPage.tsx (2)
42-68: LGTM!
chatPairs로직이 연속된 사용자 메시지나 마지막 사용자 메시지 등 엣지 케이스를 적절히 처리합니다.
177-188: LGTM!반응형 레이아웃 처리가 적절합니다. xl 이상에서는 HomeQueryBox, 그 이하에서는 UserChatBox를 표시합니다. 이전 리뷰에서 지적된
onSubmitprop 이슈가 해결된 것으로 보입니다.src/mocks/fixtures/chatReasoning.ts (1)
14-70: LGTM! Mock 데이터가 잘 구조화되어 있습니다.채팅 추론 데이터가 일관성 있게 정의되어 있습니다:
- 각 reasoningSteps의 linkIds가 최상위 linkIds와 올바르게 매핑됩니다
- 한국어 텍스트가 자연스럽고 맥락에 적합합니다
- 세 가지 시나리오(웹 접근성, Gemini, IT 트렌드)가 다양한 테스트 케이스를 커버합니다
| export default function Chat() { | ||
| const params = useSearchParams(); | ||
| const { setChats, setActiveChat } = useChatStore(); | ||
| const { setSelectedLink } = useChatRightPanelStore(); | ||
| const [message, setMessage] = useState(''); | ||
| const useMockData = process.env.NEXT_PUBLIC_USE_MOCKS === 'true'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
fd page.tsx src/app/\(route\)/chatRepository: Team-SoFa/linkiving
Length of output: 126
🏁 Script executed:
cat -n src/app/\(route\)/chat/page.tsxRepository: Team-SoFa/linkiving
Length of output: 246
🏁 Script executed:
head -5 src/app/\(route\)/chat/ChatPage.tsxRepository: Team-SoFa/linkiving
Length of output: 286
🏁 Script executed:
head -20 src/app/\(route\)/chat/ChatPage.tsxRepository: Team-SoFa/linkiving
Length of output: 1016
useSearchParams() 사용 시 Suspense 경계 필요
프로덕션 빌드 시 Client Component에서 useSearchParams()를 호출하는 정적 페이지는 Suspense 경계로 감싸야 하며, 그렇지 않으면 빌드가 실패합니다. 정적으로 렌더링되는 경로에서 useSearchParams() 호출 시 가장 가까운 Suspense 경계까지의 Client Component 트리가 클라이언트 사이드 렌더링되어, 페이지의 일부는 정적으로 렌더링되고 useSearchParams()를 사용하는 동적 부분만 클라이언트 사이드 렌더링될 수 있습니다. page.tsx에서 <Chat /> 컴포넌트를 Suspense로 감싸거나, ChatPage에서 useSearchParams 사용 부분을 더 작은 자식 컴포넌트로 추출하여 Suspense로 감싸야 합니다.
🤖 Prompt for AI Agents
In src/app/(route)/chat/ChatPage.tsx around lines 14-19, useSearchParams() is
being called in a Client Component that can be rendered on a static page and
thus must be surrounded by a Suspense boundary to avoid production build
failures; fix it by either (A) moving the useSearchParams() logic into a smaller
child Client Component with "use client" at the top and render that child inside
a <Suspense fallback={...}> wrapper, or (B) wrap the <Chat /> component
invocation in the parent page.tsx with <Suspense fallback={...}> so the tree up
to the nearest Suspense becomes client-rendered; ensure the child component only
uses client hooks and provide a sensible fallback UI.
관련 이슈
PR 설명