ChatResultSummaryCard

Data Display

작업 결과 요약과 후속 액션 카드.

Usage

작업 완료 후 상태·통계·후속 액션을 한 카드에 모아 보여줄 때 사용합니다. 진행 중 상태 표시는 ChatJobProgress를 대신 사용하세요.

import

import

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

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

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

외부 패키지를 따로 설치한다. DS 의 전이 의존성에 기대지 않는다.

API Reference

ChatResultSummaryCard Props

Prop

Type

Default

title
필수

string

지정 안 함

actions

readonly ResultSummaryAction[]

지정 안 함

followUps

readonly string[]

지정 안 함

icon

ReactNode

지정 안 함

labels

Partial<ChatResultSummaryLabels>

지정 안 함

onFollowUp

(prompt: string) => void

지정 안 함

stats

{ additions: number; deletions: number; }

지정 안 함

status

"success" | "partial" | "failed"

success

summary

string

지정 안 함

같은 패밀리

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

예제

기본 사용

코드

import type { ComponentProps } from 'react';
import { ChatResultSummaryCard } from '@mildang/design-system/unofficial/Chat';
type Props = ComponentProps<typeof ChatResultSummaryCard>;
const DEFAULT_ARGS: Props = {
  title: '로그인 실패 시 재시도 로직 추가',
  summary:
    '토큰 만료(401)에서만 한 번 재시도하도록 인터셉터를 고쳤습니다. 기존 재시도 루프가 네트워크 오류까지 삼키던 문제도 함께 정리했습니다.',
  stats: { additions: 46, deletions: 12 },
  actions: [
    { label: 'PR 열기', href: 'https://example.com/pr/1281', target: '_blank', primary: true },
    { label: '변경 파일 보기', onClick: () => {} },
  ],
  followUps: ['테스트도 추가해줘', '리팩터링 근거 설명해줘', '스테이지에 배포해줘'],
  onFollowUp: () => {},
};
export default function ResultSummaryCardDefaultExample(props: Partial<Props> = {}) {
  return <ChatResultSummaryCard {...DEFAULT_ARGS} {...props} />;
}

일부 완료

코드

import type { ComponentProps } from 'react';
import { ChatResultSummaryCard } from '@mildang/design-system/unofficial/Chat';
type Props = ComponentProps<typeof ChatResultSummaryCard>;
const DEFAULT_ARGS: Props = {
  title: '테스트 3건 중 2건 통과',
  summary: 'ChatDiffStat 스냅샷 1건이 여전히 실패합니다. 원인은 아직 좁히지 못했습니다.',
  status: 'partial',
  stats: { additions: 18, deletions: 4 },
  followUps: ['남은 1건 원인 찾아줘', '실패 로그 보여줘'],
  onFollowUp: () => {},
};
export default function ResultSummaryCardPartialExample(props: Partial<Props> = {}) {
  return <ChatResultSummaryCard {...DEFAULT_ARGS} {...props} />;
}

실패

코드

import type { ComponentProps } from 'react';
import { ChatResultSummaryCard } from '@mildang/design-system/unofficial/Chat';
type Props = ComponentProps<typeof ChatResultSummaryCard>;
const DEFAULT_ARGS: Props = {
  title: '빌드 실패로 작업을 중단했습니다',
  summary: 'turbo run ship 이 panda.buildinfo.json 을 찾지 못했습니다. 의존성 빌드를 먼저 돌려야 합니다.',
  status: 'failed',
  actions: [{ label: '로그 보기', onClick: () => {} }],
};
export default function ResultSummaryCardFailedExample(props: Partial<Props> = {}) {
  return <ChatResultSummaryCard {...DEFAULT_ARGS} {...props} />;
}

최소 구성

코드

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

const ResultSummaryCardMinimalExample = () => (
  <div className={css({ display: 'flex', flexDirection: 'column', gap: '16' })}>
    <ChatResultSummaryCard title="정리 완료" />
    <ChatResultSummaryCard
      title="onFollowUp 없이 칩만 준 경우"
      summary="칩이 표시 전용이 됩니다 — role=button 도 키보드 핸들러도 붙지 않습니다."
      followUps={['이어서 할 일 A', '이어서 할 일 B']}
    />
  </div>
);

export default ResultSummaryCardMinimalExample;

코드 변경 요약

코드

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

const ResultSummaryCardCodexStyleExample = () => (
  <div className={css({ display: 'flex', flexDirection: 'column', gap: '12' })}>
    <ChatResultSummaryCard
      title="resolveToolStatus 에서 requires-action 분리"
      summary={
        'requires-action 을 running 으로 뭉개던 분기를 reason 별로 갈랐습니다.\n' +
        'interrupt 는 승인 카드로, tool-calls 는 대기 배지로 갑니다.'
      }
      stats={{ additions: 3, deletions: 1 }}
      actions={[
        {
          label: 'PR #1284',
          href: 'https://example.com/pr/1284',
          target: '_blank',
          primary: true,
        },
        { label: '리뷰 요청', onClick: () => {} },
      ]}
      followUps={['같은 패턴 다른 툴에도 적용해줘', '스토리 추가해줘']}
      onFollowUp={() => {}}
    />

    <ChatCodeDiff
      filename="src/chat/Tool/resolveToolStatus.ts"
      additions={3}
      deletions={1}
      lines={[
        { kind: 'context', text: 'export function resolveToolStatus(part: ToolCallPart) {' },
        { kind: 'removed', text: '  if (status.type === "requires-action") return "running";' },
        { kind: 'added', text: '  if (status.type === "requires-action") {' },
        {
          kind: 'added',
          text: '    return status.reason === "interrupt" ? "interrupt" : "awaiting";',
        },
        { kind: 'added', text: '  }' },
        { kind: 'context', text: '}' },
      ]}
    />
  </div>
);

export default ResultSummaryCardCodexStyleExample;

makeAssistantToolUI 데이터 연동

코드

import { css } from '@mildang/styled-system/css';

const REGISTRATION = `// 앱 소유 — DS 는 등록하지 않고 등록 가능한 부품만 export 한다(AD-1).
import { makeAssistantDataUI, useAui } from '@assistant-ui/react';
import { ChatResultSummaryCard } from '@mildang/design-system/unofficial/Chat';
import type { ResultSummaryData } from '@mildang/design-system/unofficial/Chat';

// 서버가 흘려보내는 data part 페이로드가 곧 컴포넌트 prop 이다(AD-3).
// 콜백(onFollowUp / action.onClick)은 JSON 으로 올 수 없으므로 render 에서 덧붙인다.
export const ResultSummaryUI = makeAssistantDataUI<ResultSummaryData>({
  type: 'result-summary',
  render: ({ data }) => {
    const composer = useAui().thread.composer;

    return (
      <ChatResultSummaryCard
        {...data}
        onFollowUp={(prompt) => {
          composer.setText(prompt);
          composer.send();
        }}
      />
    );
  },
});

// <AssistantRuntimeProvider> 아래 어디든 한 번 마운트해 두면 된다.
function Registry() {
  return <ResultSummaryUI />;
}

// 서버 페이로드 예시 — 전부 JSON 직렬화 가능한 값이다.
// {
//   "title": "로그인 실패 시 재시도 로직 추가",
//   "summary": "토큰 만료(401)에서만 한 번 재시도하도록 …",
//   "status": "success",
//   "stats": { "additions": 46, "deletions": 12 },
//   "actions": [{ "label": "PR 열기", "href": "https://…", "target": "_blank", "primary": true }],
//   "followUps": ["테스트도 추가해줘", "스테이지에 배포해줘"]
// }`;

const ResultSummaryCardMakeAssistantDataExample = () => (
  <pre
    // 가로 스크롤 영역은 키보드로도 닿아야 한다(axe `scrollable-region-focusable`).
    tabIndex={0}
    className={css({
      margin: '0',
      overflowX: 'auto',
      borderRadius: 'lg',
      backgroundColor: 'neutral.ghostBg.base',
      padding: '16',
      textStyle: 'chat-small-text-R',
      fontFamily: 'monospace',
      color: 'neutral.text.base',
    })}
  >
    {REGISTRATION}
  </pre>
);

export default ResultSummaryCardMakeAssistantDataExample;