ChainOfThought

Data Display

AI 사고 과정을 접고 펼치는 타임라인.

Usage

어시스턴트의 추론 과정을 트리거로 접고 펼치는 타임라인으로 보여줄 때 사용한다.

import

import

import { ChainOfThought } from '@mildang/design-system/unofficial/Chat';

ChainOfThought 하나만 가져오면 ChainOfThought.Trigger · ChainOfThought.Content · ChainOfThought.Preview · ChainOfThought.Steps · ChainOfThought.Step 를 그 아래에서 쓸 수 있다.

예제를 복사해 쓸 때 필요한 준비

@mildang/styled-system 은 이 저장소에서 Panda 가 생성하는 산출물이다. 저장소 안에서는 turbo run ship 이후 쓸 수 있고, 패키지 소비자는 자기 Panda 산출물이나 다른 레이아웃 수단으로 바꿔야 한다.

Anatomy

tsx

import { ChainOfThought } from '@mildang/design-system/unofficial/Chat';

export default function Example() {
  return (
    <ChainOfThought.Trigger />
    <ChainOfThought.Content>
      <ChainOfThought.Preview />
      <ChainOfThought.Steps>
        <ChainOfThought.Step />
      </ChainOfThought.Steps>
    </ChainOfThought.Content>
  );
}

부품

필수 여부

반복

위치

ChainOfThought.Trigger

필수

ChainOfThought.Content

필수

ChainOfThought.Trigger 다음에 둔다.

ChainOfThought.Preview

조건부

스트리밍 중이면서 펼쳐진 상태(isPreview)에서만 의미가 있다.

ChainOfThought.Content 안에 둔다.

ChainOfThought.Steps

필수

ChainOfThought.Content 안에 둔다.

ChainOfThought.Step

필수

여러 개 가능

ChainOfThought.Steps 안에 둔다.

  • 열림/닫힘과 프리뷰 동작은 assistant-ui elements의 reasoning 동작을 그대로 따른다.
  • 스트리밍이 끝나면 자동으로 닫히지만, 사용자가 수동으로 토글하면 그 선택이 이후에도 우선한다.

API Reference

ChainOfThought Props

Prop

Type

Default

isStreaming

boolean

지정 안 함

ChainOfThought.Trigger

전체 타임라인을 접고 펼치는 트리거다. 스트리밍 중에는 라벨에 shimmer가 흐른다.

Prop

Type

Default

children

ReactNode

지정 안 함

duration

number

지정 안 함

icon

ReactNode

지정 안 함

label

ReactNode

지정 안 함

ChainOfThought.Content

접기/펼치기 애니메이션이 적용되는 본문 컨테이너다.

공개 Props 없음

ChainOfThought.Preview

스트리밍 중 펼쳐진 상태에서 최신 내용을 바닥에 고정해 보여준다.

공개 Props 없음

ChainOfThought.Steps

개별 사고 단계를 모아 두는 목록 컨테이너다.

공개 Props 없음

ChainOfThought.Step

사고 과정의 단계 하나를 표시한다.

Prop

Type

Default

children

ReactNode

지정 안 함

label

ReactNode

지정 안 함

같은 패밀리

@mildang/design-system/unofficial/Chat 에서 같이 내보내는 컴포넌트다.

예제

펼침

트리거와 타임라인 스텝이 펼쳐진 기본 상태다.

import { ChainOfThought } from '@mildang/design-system/unofficial/Chat';
import { css } from '@mildang/styled-system/css';

const wrapper = css({ padding: '16' });

const steps = (
  <ChainOfThought.Content>
    <ChainOfThought.Steps>
      <ChainOfThought.Step label="문제 파악">
        사용자가 이차방정식의 근을 구하는 방법을 물었어요.
      </ChainOfThought.Step>
      <ChainOfThought.Step label="풀이 전략">
        근의 공식과 인수분해 두 가지를 비교해 설명하기로 했어요.
      </ChainOfThought.Step>
      <ChainOfThought.Step label="답안 구성">
        단계별 예시와 최종 답을 정리했어요.
      </ChainOfThought.Step>
    </ChainOfThought.Steps>
  </ChainOfThought.Content>
);

const ChainOfThoughtExpandedExample = () => (
    <div className={wrapper}>
      <ChainOfThought defaultOpen>
        <ChainOfThought.Trigger duration={4} />
        {steps}
      </ChainOfThought>
    </div>
  );

export default ChainOfThoughtExpandedExample;

접힘

트리거만 보이고 클릭하면 펼쳐지는 기본 닫힘 상태다.

import { ChainOfThought } from '@mildang/design-system/unofficial/Chat';
import { css } from '@mildang/styled-system/css';

const wrapper = css({ padding: '16' });

const steps = (
  <ChainOfThought.Content>
    <ChainOfThought.Steps>
      <ChainOfThought.Step label="문제 파악">
        사용자가 이차방정식의 근을 구하는 방법을 물었어요.
      </ChainOfThought.Step>
      <ChainOfThought.Step label="풀이 전략">
        근의 공식과 인수분해 두 가지를 비교해 설명하기로 했어요.
      </ChainOfThought.Step>
      <ChainOfThought.Step label="답안 구성">
        단계별 예시와 최종 답을 정리했어요.
      </ChainOfThought.Step>
    </ChainOfThought.Steps>
  </ChainOfThought.Content>
);

const ChainOfThoughtCollapsedExample = () => (
    <div className={wrapper}>
      <ChainOfThought>
        <ChainOfThought.Trigger duration={7} />
        {steps}
      </ChainOfThought>
    </div>
  );

export default ChainOfThoughtCollapsedExample;

스트리밍

생성 중 트리거 라벨에 shimmer가 흐르는 상태다.

import { ChainOfThought } from '@mildang/design-system/unofficial/Chat';
import { css } from '@mildang/styled-system/css';

const wrapper = css({ padding: '16' });

const steps = (
  <ChainOfThought.Content>
    <ChainOfThought.Steps>
      <ChainOfThought.Step label="문제 파악">
        사용자가 이차방정식의 근을 구하는 방법을 물었어요.
      </ChainOfThought.Step>
      <ChainOfThought.Step label="풀이 전략">
        근의 공식과 인수분해 두 가지를 비교해 설명하기로 했어요.
      </ChainOfThought.Step>
      <ChainOfThought.Step label="답안 구성">
        단계별 예시와 최종 답을 정리했어요.
      </ChainOfThought.Step>
    </ChainOfThought.Steps>
  </ChainOfThought.Content>
);

const ChainOfThoughtStreamingExample = () => (
    <div className={wrapper}>
      <ChainOfThought isStreaming defaultOpen>
        <ChainOfThought.Trigger />
        {steps}
      </ChainOfThought>
    </div>
  );

export default ChainOfThoughtStreamingExample;

스트리밍 프리뷰

스트리밍 중 펼쳐진 상태에서 내용이 바닥에 고정되는 예시다.

import { useEffect, useState } from 'react';
import { css } from '@mildang/styled-system/css';
import { Button } from '@mildang/design-system/Button';
import { ChainOfThought } from '@mildang/design-system/unofficial/Chat';

const wrapper = css({ padding: '16' });

const TRACE = [
  '사용자가 이차방정식 x² - 5x + 6 = 0 의 근을 물었다.',
  '먼저 판별식을 계산한다. D = b² - 4ac = 25 - 24 = 1 > 0 이므로 서로 다른 두 실근을 가진다.',
  '인수분해가 가능한지 본다. 곱이 6, 합이 -5 인 두 정수는 -2 와 -3 이다.',
  '따라서 (x - 2)(x - 3) = 0 이고 x = 2 또는 x = 3 이다.',
  '근의 공식으로도 검산한다. x = (5 ± 1) / 2 = 3 또는 2. 일치한다.',
  '답변에서는 인수분해를 먼저 보여주고 근의 공식을 검산으로 붙이기로 한다.',
  '학생이 판별식을 아직 안 배웠을 수 있으니 그 부분은 부연 설명으로 남긴다.',
  '마지막으로 유사 문제 한 개를 연습용으로 덧붙인다.',
].join('\n\n');

// 스트리밍 프리뷰 — Preview 슬롯의 실제 동작 확인용.
//
// - 스트리밍 시작 → 자동 열림, 종료 → 자동 닫힘
// - 트리거 라벨만 shimmer(본문은 평문이라 그대로 읽힌다)
// - 본문이 높이 상한을 넘으면 자체 스크롤 + 상하 fade, 새 토큰을 따라 바닥 고정
// - 스트리밍 중 위로 스크롤하면 고정이 풀리고, 다시 바닥에 닿으면 재고정
// - 트리거를 한 번 수동 토글하면 그 뒤로는 스트리밍 상태와 무관하게 사용자 선택이 유지된다
const ChainOfThoughtStreamingPreviewExample = () => {
  const [chars, setChars] = useState(0);
  const isStreaming = chars < TRACE.length;

  useEffect(() => {
    if (!isStreaming) return;
    const id = setInterval(() => setChars((n) => Math.min(n + 6, TRACE.length)), 30);
    return () => clearInterval(id);
  }, [isStreaming]);

  return (
    <div className={wrapper}>
      <Button size="sm" variant="secondary" onClick={() => setChars(0)}>
        다시 스트리밍
      </Button>
      <ChainOfThought isStreaming={isStreaming} sx={{ marginTop: '16' }}>
        <ChainOfThought.Trigger duration={isStreaming ? undefined : 6} />
        <ChainOfThought.Content aria-busy={isStreaming}>
          <ChainOfThought.Preview>{TRACE.slice(0, chars)}</ChainOfThought.Preview>
        </ChainOfThought.Content>
      </ChainOfThought>
    </div>
  );
};

export default ChainOfThoughtStreamingPreviewExample;

긴 사고 과정

단계가 많은 긴 추론 과정을 보여주는 예시다.

import { ChainOfThought } from '@mildang/design-system/unofficial/Chat';
import { css } from '@mildang/styled-system/css';

const wrapper = css({ padding: '16' });

const TRACE = [
  '사용자가 이차방정식 x² - 5x + 6 = 0 의 근을 물었다.',
  '먼저 판별식을 계산한다. D = b² - 4ac = 25 - 24 = 1 > 0 이므로 서로 다른 두 실근을 가진다.',
  '인수분해가 가능한지 본다. 곱이 6, 합이 -5 인 두 정수는 -2 와 -3 이다.',
  '따라서 (x - 2)(x - 3) = 0 이고 x = 2 또는 x = 3 이다.',
  '근의 공식으로도 검산한다. x = (5 ± 1) / 2 = 3 또는 2. 일치한다.',
  '답변에서는 인수분해를 먼저 보여주고 근의 공식을 검산으로 붙이기로 한다.',
  '학생이 판별식을 아직 안 배웠을 수 있으니 그 부분은 부연 설명으로 남긴다.',
  '마지막으로 유사 문제 한 개를 연습용으로 덧붙인다.',
].join('\n\n');

const ChainOfThoughtLongTraceExample = () => (
    <div className={wrapper}>
      <ChainOfThought defaultOpen>
        <ChainOfThought.Trigger duration={6} />
        <ChainOfThought.Content>
          <ChainOfThought.Preview>{TRACE}</ChainOfThought.Preview>
        </ChainOfThought.Content>
      </ChainOfThought>
    </div>
  );

export default ChainOfThoughtLongTraceExample;

커스텀 라벨

트리거 라벨 문구를 커스텀으로 바꾼 예시다.

import { ChainOfThought } from '@mildang/design-system/unofficial/Chat';
import { css } from '@mildang/styled-system/css';

const wrapper = css({ padding: '16' });

const steps = (
  <ChainOfThought.Content>
    <ChainOfThought.Steps>
      <ChainOfThought.Step label="문제 파악">
        사용자가 이차방정식의 근을 구하는 방법을 물었어요.
      </ChainOfThought.Step>
      <ChainOfThought.Step label="풀이 전략">
        근의 공식과 인수분해 두 가지를 비교해 설명하기로 했어요.
      </ChainOfThought.Step>
      <ChainOfThought.Step label="답안 구성">
        단계별 예시와 최종 답을 정리했어요.
      </ChainOfThought.Step>
    </ChainOfThought.Steps>
  </ChainOfThought.Content>
);

const ChainOfThoughtCustomLabelExample = () => (
    <div className={wrapper}>
      <ChainOfThought defaultOpen>
        <ChainOfThought.Trigger label="풀이 과정 보기" />
        {steps}
      </ChainOfThought>
    </div>
  );

export default ChainOfThoughtCustomLabelExample;