ChatInlineCitation

Data Display

본문 안에 삽입하는 출처 각주 마커.

Usage

문장 중간에 짧은 출처 번호 표시가 필요할 때 사용합니다. 문서 페이지 근거는 ChatDocumentReference, 검색 청크 나열은 ChatRetrievalChunks를 대신 사용하세요.

import

import

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

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

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

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

API Reference

ChatInlineCitation Props

Prop

Type

Default

index
필수

number

지정 안 함

source
필수

CitationSource

지정 안 함

labels

Partial<ChatInlineCitationLabels>

지정 안 함

onOpenChange

(open: boolean) => void

지정 안 함

open

boolean

지정 안 함

side

"bottom" | "left" | "right" | "top"

top

같은 패밀리

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

예제

열린 상태

코드

import type { ComponentProps } from 'react';
import { ChatInlineCitation } from '@mildang/design-system/unofficial/Chat';
type Props = ComponentProps<typeof ChatInlineCitation>;
const DEFAULT_ARGS: Props = {
  index: 0,
  source: {
    domain: 'react.dev',
    title: 'You Might Not Need an Effect',
    snippet: '렌더 중에 계산할 수 있는 값을 Effect 로 동기화하면 한 프레임 늦은 상태가 화면에 남는다.',
  },
  open: true,
};
export default function InlineCitationOpenExample(props: Partial<Props> = {}) {
  return <ChatInlineCitation {...DEFAULT_ARGS} {...props} />;
}

기본 사용

코드

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

const SOURCES: readonly CitationSource[] = [
  {
    domain: 'react.dev',
    title: 'You Might Not Need an Effect',
    snippet: '렌더 중에 계산할 수 있는 값을 Effect 로 동기화하면 한 프레임 늦은 상태가 화면에 남는다.',
  },
  {
    domain: 'tanstack.com',
    title: 'Optimistic Updates',
    snippet: '낙관적 갱신은 서버 확인 전에 캐시를 먼저 바꾸고, 실패하면 이전 스냅샷으로 되돌린다.',
  },
];

const paragraph = css({
  maxWidth: '420px',
  margin: '0',
  textStyle: 'chat-body',
  color: 'primary.text.base',
});

const InlineCitationDefaultExample = () => (
  <p className={paragraph}>
    낙관적 갱신은 서버가 쓰기를 확인하는 동안에도 스레드를 반응하게 유지한다
    <ChatInlineCitation index={0} source={SOURCES[0]} />. 스토어가 이미 모든 구독자에게 일관된 스냅샷을
    노출하므로
    <ChatInlineCitation index={1} source={SOURCES[1]} />, 추가 조정 패스는 필요 없다.
  </p>
);

export default InlineCitationDefaultExample;

열림 상태 제어

코드

import { useState } from 'react';
import { css } from '@mildang/styled-system/css';
import { ChatInlineCitation } from '@mildang/design-system/unofficial/Chat';
import { CitationSource } from '@mildang/design-system/unofficial/Chat';

const SOURCES: readonly CitationSource[] = [
  {
    domain: 'react.dev',
    title: 'You Might Not Need an Effect',
    snippet: '렌더 중에 계산할 수 있는 값을 Effect 로 동기화하면 한 프레임 늦은 상태가 화면에 남는다.',
  },
  {
    domain: 'tanstack.com',
    title: 'Optimistic Updates',
    snippet: '낙관적 갱신은 서버 확인 전에 캐시를 먼저 바꾸고, 실패하면 이전 스냅샷으로 되돌린다.',
  },
];

const paragraph = css({
  maxWidth: '420px',
  margin: '0',
  textStyle: 'chat-body',
  color: 'primary.text.base',
});

/** 원본과 같은 계약 — 열린 각주 하나를 호출자가 소유한다. */
const ControlledDemo = () => {
  const [openIndex, setOpenIndex] = useState<number | null>(0);

  return (
    <div className={css({ display: 'flex', flexDirection: 'column', gap: '12' })}>
      <p className={paragraph}>
        낙관적 갱신은 스레드를 반응하게 유지한다
        {SOURCES.map((source, index) => (
          <ChatInlineCitation
            key={source.domain}
            index={index}
            source={source}
            open={openIndex === index}
            onOpenChange={(next) => setOpenIndex(next ? index : null)}
          />
        ))}
        . 열린 마커는 전경색으로 칠해져 어느 각주가 열렸는지 문단 안에서 바로 보인다.
      </p>
      <span className={css({ textStyle: 'chat-small-text-R', color: 'neutral.text.low' })}>
        openIndex: {openIndex ?? 'null'}
      </span>
    </div>
  );
};

const InlineCitationControlledExample = () => <ControlledDemo />;

export default InlineCitationControlledExample;

source part 배선

코드

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

const REGISTRATION = `// source part 와의 연결 — 앱 소유(AD-1).
// 마커가 가리키는 출처 목록은 보통 \`source\` part 로 들어오고,
// 인용 구절(snippet)은 part 에 없는 값이라 서버가 함께 흘려보낸다.
import type { SourceMessagePartComponent } from '@assistant-ui/react';
import { ChatInlineCitation } from '@mildang/design-system/unofficial/Chat';
import type { CitationSource } from '@mildang/design-system/unofficial/Chat';

// 앱이 소유하는 정규화 — source part → CitationSource
export const toCitationSource = (
  part: Parameters<SourceMessagePartComponent>[0],
  snippet: string,
): CitationSource => ({
  domain: new URL(part.url).hostname.replace(/^www\\./, ''),
  title: part.title ?? '',
  snippet,
});

// 본문에 섞는 건 텍스트를 소유한 쪽(마크다운 렌더러 등)이 한다.
// DS 는 마커 하나만 제공하고 문단을 만들지 않는다.
<p>
  {segments.map((segment) =>
    segment.type === 'text' ? (
      segment.text
    ) : (
      <ChatInlineCitation
        key={segment.index}
        index={segment.index}
        source={sources[segment.index]}
      />
    ),
  )}
</p>`;

const InlineCitationSourcePartWiringExample = () => (
  <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 InlineCitationSourcePartWiringExample;