MessageWithSources
먼저 보고, 그다음 계약을 읽습니다
MessageBubble 확장 — 본문에 출처 인용·신뢰도 배지·토큰 메타·복사 버튼을 합친 RAG 응답 카드.
Component intent의도와 경계 읽기
MessageBubble(텍스트) 위에 신뢰 가능한 AI 응답에 필요한 네 가지를 얹는다: (1) sources <details> 인용 목록, (2) 우상단 confidence 배지(0..1 → red/amber/green band), (3) footer 의 토큰/비용 metabar, (4) 복사 버튼. Cortex RecallChat 의 details+copy+confidence 패턴을 SSOT 로 일반화한 것으로, override 가 아니라 composition 이다 — role/avatar/label 등 MessageBubbleProps 를 그대로 상속(onCopy 만 자체 시그니처로 교체). Streaming-aware: sources/tokens 가 아직 undefined 면 해당 영역이 아예 그려지지 않아 progressive disclosure 가 자연스럽다.
예제
SSOT에 등록된 실제 API 기준 snippet입니다. standalone 배지만 독립 실행 단위이며, fragment는 주변 state·handler 문맥을 생략합니다.
import { MessageWithSources } from "@axe/ui";<MessageWithSources role="assistant" confidence={0.82} tokens={{ input: 1240, output: 320, cost: 0.0089 }} sources={[ { id: "1", type: "doc", title: "2024 감사보고서", snippet: "매출 142억…", href: "/docs/audit", confidence: 0.9 }, { id: "2", type: "memory", title: "전분기 IC 메모", confidence: 0.6 }, ]}> 2024년 매출은 142억 원으로 전년 대비 18% 증가했습니다.</MessageWithSources>import { MessageWithSources } from "@axe/ui";<MessageWithSources role="assistant" copyText="요약 결과"> 요약 결과</MessageWithSources><div class="axe-message axe-message--assistant axe-message-with-sources"> <div class="axe-message__avatar" aria-hidden="true">A</div> <div class="axe-message__body"> <div class="axe-message__content"> <div class="axe-message-with-sources__head"> <div class="axe-message-with-sources__body-text">2024년 매출은 142억 원…</div> <div class="axe-message-with-sources__head-actions"> <span class="axe-message-with-sources__confidence axe-message-with-sources__confidence--high">82%</span> </div> </div> <details class="axe-message-with-sources__sources"> <summary class="axe-message-with-sources__sources-summary">출처 1건</summary> <ul class="axe-message-with-sources__sources-list"> <li class="axe-message-with-sources__source"> <span class="axe-message-with-sources__source-type">doc</span> <a class="axe-message-with-sources__source-title" href="/docs/audit">2024 감사보고서</a> </li> </ul> </details> </div> </div></div>Props
TSX 소스가 진실입니다. 주요 export 컴포넌트의 public API만 노출합니다.
| 이름 | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
role | "user" | "assistant" | "system" | 필수 | — | MessageBubble 상속 — 아바타/라벨/정렬 결정. |
sources | SourceItem[] | — | — | 인용 목록. undefined 또는 빈 배열이면 <details> 미렌더. SourceItem = { id, title, snippet?, href?, confidence?, type? }. |
tokens | TokenMeta | — | — | 토큰/비용 메타. footer 에 in·out·cost 로 표시. TokenMeta = { input?, output?, cost? }(cost=USD). |
confidence | number | — | — | 0..1. 우상단 배지로 표시. <0.5 low(red) · <0.8 med(amber) · ≥0.8 high(green). |
onCopy | (text: string) => void | — | — | 복사 콜백. 미지정 시 navigator.clipboard.writeText 로 fallback. |
copyText | string | — | — | 복사에 넘길 텍스트. 미지정 시 children 이 string 이면 그것, 아니면 빈 문자열. |
defaultSourcesOpen | boolean | — | false | sources <details> 를 처음부터 펼칠지. |
footer | React.ReactNode | — | — | 토큰 metabar 옆(또는 대신) 붙는 자유 footer — 예: action 버튼. |
children | React.ReactNode | — | — | 본문. string 이면 copyText 기본값으로도 쓰인다. |
avatar / label / showRole | MessageBubbleProps | — | — | MessageBubble 상속 props 그대로 통과. |
...rest | Omit<MessageBubbleProps, "onCopy"> | — | — | 그 외 MessageBubble/HTMLAttributes 는 루트로 전파. |
.axe-* 클래스 계약
React 밖에서도 같은 표면을 그리는 공개 계약입니다. stable은 minor 버전 안에서 이름을 바꾸지 않습니다.
| 클래스 | 안정성 | 용도 |
|---|---|---|
.axe-message-with-sources | stable | 루트 modifier(axe-message 위). content 를 세로 flex 로 재배치. |
.axe-message-with-sources__head | stable | 본문 텍스트 + 우상단 액션(confidence/copy)을 나누는 상단 행. |
.axe-message-with-sources__body-text | stable | 본문 텍스트 영역(pre-wrap, break-word). |
.axe-message-with-sources__head-actions | stable | 우상단 confidence 배지 + 복사 버튼 그룹. |
.axe-message-with-sources__confidence | stable | confidence 배지. --low/--med/--high 변형이 band 색(danger/warning/success-soft)을 결정. |
.axe-message-with-sources__copy | stable | 복사 버튼(ghost, focus-visible ring). |
.axe-message-with-sources__sources | stable | 출처 <details> 컨테이너(상단 구분선). |
.axe-message-with-sources__sources-summary | stable | <summary> '출처 N건' 토글(▸ 마커 회전). |
.axe-message-with-sources__sources-list | stable | 출처 항목 <ul>. |
.axe-message-with-sources__source | stable | 개별 출처 <li>(type|title|conf 3열 grid). |
.axe-message-with-sources__source-type | stable | 출처 분류 칩(doc/memory/web 등). |
.axe-message-with-sources__source-title | stable | 출처 제목 — href 있으면 <a target=_blank>, 없으면 <span>. |
.axe-message-with-sources__source-snippet | stable | 출처 발췌문(italic, 전 열 span). |
.axe-message-with-sources__source-conf | stable | 출처별 신뢰도 배지. --low/--med/--high band 색. |
.axe-message-with-sources__footer | stable | 토큰 metabar + 자유 footer 를 담는 하단 행(파선 구분). |
.axe-message-with-sources__tokens | stable | in·out·cost 토큰 metabar(mono). |
.axe-message-with-sources__token | stable | 토큰 항목 하나(label + val). |
.axe-message-with-sources__token-label | stable | 토큰 라벨(in/out/cost, uppercase muted). |
.axe-message-with-sources__token-val | stable | 토큰 값(axe-tabular-nums 로 자릿수 정렬). |
접근성
키보드, ARIA, 구현 노트를 함께 검토합니다.
- Enter / Space — sources <summary> 펼침/접힘(네이티브 <details>)
- Enter / Space — 복사 버튼 실행(네이티브 <button>)
- Tab — 출처 링크(<a>)로 순차 이동
출처는 네이티브 <details>/<summary> 라 펼침 상태를 스스로 고지한다(수동 aria-expanded 불요). confidence 배지는 aria-label·title 로 'confidence NN%' 를 노출해 색 band 에만 의존하지 않는다. 복사 버튼은 aria-label="복사" 이며, 복사 완료 고지는 버튼과 분리된 별도 visually-hidden span(role="status" aria-live="polite")이 담당한다 — 버튼 자체에 aria-live 를 박으면 hover/focus 마다 잡음이 생기므로 의도적으로 분리했다.
출처 링크는 target="_blank" 에 rel="noopener noreferrer" 가 붙는다. sources/tokens 가 undefined 면 해당 영역이 렌더되지 않아, 스트리밍 도중 메타가 늦게 도착해도 레이아웃이 점진적으로 채워진다.