Markdown

AIの回答向けに作られたMarkdownです。滑らかにストリーミングされ、入力途中の構文を表示しません。リンクできる見出し、MarkdownやCSVとしてコピーできるテーブル、GitHubのコールアウト、タスクリスト、脚注、画像、インラインの引用、完全なコードブロックに対応します。

モデルはMarkdownで回答し、1トークンずつ書きます。単純なレンダラーは、ストリーミング中に生のアスタリスク、途中までのテーブル、閉じられていないコードフェンスを一瞬表示してしまいます。Markdownは未完了の構文を補完しながら進み、Streamingブロックのようにテキストのペースを調整するため、書式はその場で現れます。

各要素は、HextaUIの他の部品から作られています。コードフェンスはCode Blockに、[!NOTE] のようなGitHubのコールアウトはAlertに、タスクリストは読み取り専用のCheckboxになり、テーブルは横にスクロールし、MarkdownやCSVとしてコピーするメニューが付きます。sourcesを渡すと [1] が引用チップになり、コード内の記号はそのまま残ります。

見出しにはテキストからidが付与され、共有用のリンクアイコンも任意で付けられます。画像はプレースホルダーとキャプションつきで遅延読み込みされ、脚注はそのマークにリンクして戻り、外部リンクは新しいタブで開いてその旨を示し、テキストの方向はブロックごとに検出されます。ストリーミングされる回答だけでなく、READMEや変更履歴にも同様に使えます。

  1. Proレジストリをcomponents.jsonに追加する

    components.json
    {
      "registries": {
        "@hextaui-pro": {
          "url": "https://hextaui.com/r/pro/{name}.json",
          "headers": {
            "Authorization": "Bearer ${HEXTAUI_PRO_TOKEN}"
          }
        }
      }
    }
  2. トークンを追加する

    アカウントページでトークンを作成し、.env.local に HEXTAUI_PRO_TOKEN として設定してください。

  3. ブロックを追加する

    pnpm dlx shadcn@latest add @hextaui-pro/markdown

AI SDKと使う

useChatメッセージの各テキストパーツを描画し、source-urlパーツをその出典として渡します。パーツのstateにより、Markdownはモデルがまだ書き込み中かどうかを判断します。

"use client"

import type { UIMessage } from "ai"

import { Markdown } from "@/components/blocks/markdown/markdown"

export function AssistantMessage({ message }: { message: UIMessage }) {
  const sources = message.parts.flatMap((part) =>
    part.type === "source-url"
      ? [{ id: part.sourceId, url: part.url, title: part.title ?? part.url }]
      : []
  )

  return message.parts.map((part, index) =>
    part.type === "text" ? (
      <Markdown
        key={index}
        text={part.text}
        streaming={part.state === "streaming"}
        sources={sources}
      />
    ) : null
  )
}

静的なコンテンツ

READMEや変更履歴、保存した回答には、文字列をchildrenとして渡します。anchorsを設定すると、見出しに共有できるリンクが付きます。

import { Markdown } from "@/components/blocks/markdown/markdown"

const changelog = `## 2.4.0

> [!NOTE]
> Requires React 19.

- [x] Faster cold starts
- [x] Copy tables as CSV
- [ ] Offline mode`

export function Changelog() {
  return <Markdown anchors size="base">{changelog}</Markdown>
}

構造

外側から内側へ組み合わせるパーツ。

パーツ説明
Markdownレンダラー。文字列を受け取り、ペース調整、解析、すべての要素を処理します。
MarkdownTableコピーメニューを備えた、スクロール可能なテーブル。
MarkdownAlert[!NOTE] とその他のGitHubアラート種別のためのコールアウト。
MarkdownHeadingidと、任意のアンカーリンクを持つ見出し。
MarkdownImage読み込み、エラー、キャプションの状態を持つ画像。
プロパティ型デフォルト
textここまでのMarkdown。childrenとして渡すこともできます。
string–
streamingテキストのペースを調整し、キャレットを表示し、未完了の構文を補完します。
booleanfalse
smoothペース調整をオフにすると、受け取ったとおりにテキストを描画します。
booleantrue
sources[1] を sources[0] のチップに、というように変換します。sourcesがない場合、[1] はプレーンテキストのままです。
Source[][]
size文字サイズ。チャットではsm、記事ではbaseを使います。
"sm" | "base""sm"
anchors見出しにリンクアイコンを表示します。
booleanfalse
componentsaやimgなど、任意の要素を上書きします。ブロックが再マウントされないよう、オブジェクトはコンポーネントの外に置くなどして、安定させてください。
Components–
classNameラッパーのクラス。
string–
キーアクション
Tabリンク、引用、見出しのアンカー、テーブルのコピーメニュー、コードブロックのアクションを、読む順に移動します。
Enterリンクを開くか、テーブルのコピーメニューを開きます。
↑↓Copy as MarkdownとCopy as CSVの間を移動します。
  • ラッパーはストリーミング中aria-busyとしてマークされるため、スクリーンリーダーはテキストが落ち着くのを待ちます。
  • コールアウトは、ライブのアラートのように読み上げられる代わりに、種類をラベルとするrole=noteを使います。
  • タスクリストのチェックボックスは読み取り専用でTabではスキップされるため、コントロールではなく状態として読み上げられます。
  • 外部リンクには「opens in a new tab」と示され、引用チップには出典にちなんだ名前が付き、見出しのアンカーには「Link to …」という名前が付きます。
  • 画像はaltテキストを保ち、テーブルはスクリーンリーダーでの操作のためにヘッダーセルを保ちます。
  • モーション軽減時は、キャレットの点滅が止まり、画像はフェードせずに表示されます。

使用技術

Markdown を構成する無料のHextaUIコンポーネントです。それぞれ単独でインストールできます。

コード

3 個のファイルを components/blocks/markdown に追加しました。