Skip to content

Conversation

@Goder-0
Copy link
Contributor

@Goder-0 Goder-0 commented Dec 20, 2025

관련 이슈

PR 설명

  • add chat right sidebar and room list components
  • add chat mocks and tabbed AI responses

@Goder-0 Goder-0 linked an issue Dec 20, 2025 that may be closed by this pull request
@github-actions
Copy link

@Goder-0 Goder-0 force-pushed the feature/#291-chat-sidebar-list branch from 9a308c6 to d9de8ef Compare December 20, 2025 20:47
@coderabbitai
Copy link

coderabbitai bot commented Dec 20, 2025

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)
Check name Status Explanation Resolution
Out of Scope Changes check ❓ Inconclusive ChatPage.tsx, layout.tsx 등에서 다양한 UI 컴포넌트가 통합되었으나, 이러한 변경사항들이 이슈 #291의 범위에 명시되지 않아 범위 초과 여부를 확정하기 어렵습니다. 이슈 #291의 범위가 채팅방 목록 컴포넌트 추가에 한정되는지, 전체 채팅 UI 통합까지 포함하는지 명확히 하시기 바랍니다.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목은 채팅방 목록 컴포넌트 추가와 mock 데이터 추가라는 주요 변경사항을 명확하게 요약하고 있습니다.
Description check ✅ Passed PR 설명은 템플릿을 따르고 있으며, 관련 이슈(#291)를 명시하고 주요 변경사항을 간결하게 설명하고 있습니다.
Linked Issues check ✅ Passed PR의 모든 변경사항이 이슈 #291의 요구사항을 충족합니다: ChatRoomList 컴포넌트 추가, mock 데이터 연결, 활성 상태 처리 구현되었습니다.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/#291-chat-sidebar-list

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a 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: linkCardsuseMemo로 감싸는 것을 권장합니다.

다른 파생 데이터(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

📥 Commits

Reviewing files that changed from the base of the PR and between e8e835c and 9a308c6.

📒 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.tsx
  • 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 (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 훅들이 적절한 의존성 배열과 함께 올바르게 구현되어 있습니다.

@github-actions
Copy link

@Goder-0 Goder-0 changed the title Feat: build chat layout (#291) 채팅방 목록 컴포넌트 추가 및 mock 데이터 추가 Dec 20, 2025
Copy link

@coderabbitai coderabbitai bot left a 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: 이전 리뷰 이슈가 해결되었습니다.

과거 리뷰에서 HomeQueryBoxonSubmit prop을 전달할 수 없다는 지적이 있었으나, 현재 코드에서는 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

📥 Commits

Reviewing files that changed from the base of the PR and between 9a308c6 and d9de8ef.

📒 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.tsx
  • src/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의 빈 memo prop은 현재 플레이스홀더로 예상되며, 향후 실제 데이터 연동 시 업데이트될 것으로 보입니다.

src/app/(route)/chat/ChatPage.tsx (2)

14-69: 채팅 UI 통합이 잘 구현되었습니다.

URL 파라미터 기반 활성 채팅 선택, 모의 데이터 연동, 메시지 및 링크 카드 계산 로직이 명확하게 구현되었습니다. useEffectuseMemo를 적절히 활용하여 상태 동기화와 파생 데이터 계산을 효율적으로 처리했습니다.


71-162: 채팅 레이아웃과 탭 기반 UI가 효과적으로 구현되었습니다.

채팅 메시지 페어링, 탭 기반 콘텐츠 표시('답변', '링크', '단계'), 반응형 입력 박스(HomeQueryBox/UserChatBox) 전환이 잘 구현되어 PR 목표를 달성했습니다.

@Goder-0 Goder-0 requested a review from Seong-Myeong December 20, 2025 21:04
- add chat right sidebar and room list components
- add chat mocks and tabbed AI responses
@github-actions
Copy link

Copy link

@coderabbitai coderabbitai bot left a 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.tsChatLinkCard 타입과 동일합니다. 향후 유지보수를 위해 공통 타입을 @/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: linkIdsrelatedLinks 필드의 중복을 검토하세요.

ChatReasoningResponse 타입에 linkIdsrelatedLinks 두 필드가 모두 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

📥 Commits

Reviewing files that changed from the base of the PR and between d9de8ef and 1be9781.

📒 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를 표시합니다. 이전 리뷰에서 지적된 onSubmit prop 이슈가 해결된 것으로 보입니다.

src/mocks/fixtures/chatReasoning.ts (1)

14-70: LGTM! Mock 데이터가 잘 구조화되어 있습니다.

채팅 추론 데이터가 일관성 있게 정의되어 있습니다:

  • 각 reasoningSteps의 linkIds가 최상위 linkIds와 올바르게 매핑됩니다
  • 한국어 텍스트가 자연스럽고 맥락에 적합합니다
  • 세 가지 시나리오(웹 접근성, Gemini, IT 트렌드)가 다양한 테스트 케이스를 커버합니다

Comment on lines 14 to +19
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';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

fd page.tsx src/app/\(route\)/chat

Repository: Team-SoFa/linkiving

Length of output: 126


🏁 Script executed:

cat -n src/app/\(route\)/chat/page.tsx

Repository: Team-SoFa/linkiving

Length of output: 246


🏁 Script executed:

head -5 src/app/\(route\)/chat/ChatPage.tsx

Repository: Team-SoFa/linkiving

Length of output: 286


🏁 Script executed:

head -20 src/app/\(route\)/chat/ChatPage.tsx

Repository: 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.

@Goder-0 Goder-0 requested a review from Bangdayeon December 22, 2025 15:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

채팅 좌측 사이드바에 채팅방 목록 렌더링 추가

2 participants