Code Block

AIの回答向けに作られたコードブロックです。ストリーミングに追従するシンタックスハイライト、コピー、ダウンロード、折り返し、行番号とハイライト行、承認と却下のある差分、コマンド用のターミナルを備えています。

AIの回答に含まれるコードは数文字ずつ届き、人が承認しなければならない差分であることも少なくありません。Code Blockはストリーミング中もハイライトし、読み手が上にスクロールしていなければ新しい行に追従し、コードが完成するまでコピー、ダウンロード、折り返しを邪魔にならないように控えます。

ハイライトにはShikiとGitHubのライトテーマ、ダークテーマを使用し、言語ごとに必要になったときに読み込みます。テキストが届く際には新しい行だけがトークン化されるため、長いファイルでも高速で、両方のテーマが同時に描画されるので、配色を切り替えてもちらつきません。16行を超えるブロックは「Show all」の背後に折りたたまれます。

統合差分を渡すと、旧行番号と新行番号、変更数、色付きの行が得られます。コピーすると差分ではなく新しいバージョンがコピーされます。レビューを追加すると、読み手は変更を承認または却下でき、ブロックにフォーカスがあるときは⌘↵と⌘⌫が使えます。CodeFenceは同じブロックをStreamdownに組み込み、CodeTerminalはコマンドを出力と終了コードとともに表示します。

  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/code-block

StreamdownでのMarkdown内

CodeFenceをレンダラーとして登録します。言語と、title="app/page.tsx"、{2,4-6}、showLineNumbersなどのフェンスの設定を読み取り、フェンスがまだストリーミング中でもハイライトを維持します。

"use client"

import { Streamdown } from "streamdown"

import { CodeFence, codeFenceLanguages } from "@/components/blocks/code-block/code-fence"

export function Answer({
  markdown,
  streaming,
}: {
  markdown: string
  streaming: boolean
}) {
  return (
    <Streamdown
      mode="streaming"
      isAnimating={streaming}
      plugins={{
        renderers: [{ language: codeFenceLanguages, component: CodeFence }],
      }}
    >
      {markdown}
    </Streamdown>
  )
}

変更をレビューする

提案された編集を差分として表示し、読み手が承認または却下できるようにします。ブロックにフォーカスがあるときは⌘↵と⌘⌫が使えます。

"use client"

import * as React from "react"

import { CodeBlock, type ReviewStatus } from "@/components/blocks/code-block/code-block"

export function ProposedEdit({
  patch,
  onAccept,
  onReject,
}: {
  patch: string
  onAccept: () => Promise<void>
  onReject: () => void
}) {
  const [status, setStatus] = React.useState<ReviewStatus>("pending")

  return (
    <CodeBlock
      code={patch}
      language="tsx"
      filename="components/search.tsx"
      diff
      review={{
        status,
        onAccept: async () => {
          await onAccept()
          setStatus("accepted")
        },
        onReject: () => {
          onReject()
          setStatus("rejected")
        },
      }}
    />
  )
}

単独で使う

ツール呼び出しがファイルの内容を返したときなどに、コードと言語を直接渡します。

import { CodeBlock } from "@/components/blocks/code-block/code-block"

export function FileContents({ path, contents }: { path: string; contents: string }) {
  return (
    <CodeBlock
      code={contents}
      language={path.split(".").pop()}
      filename={path}
      lineNumbers
      highlight={[3, 4]}
    />
  )
}

構造

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

パーツ説明
CodeBlockブロック本体。ヘッダー、アクション、コード、任意のレビューバー。
CodeFenceフェンス付きコードをCodeBlockに変換するStreamdownレンダラー。
CodeTerminalストリーミングされる出力と終了ステータスを持つコマンド。
CopyButton, DownloadButton, WrapToggleヘッダーのアクション。独自のヘッダー向けにエクスポートされています。
LanguageIconヘッダーで使う言語マーク。
プロパティ型デフォルト
codeソース。diffが設定されている場合は統合差分。
string–
languageShikiの言語idまたはエイリアス。tsx、py、bashなど。不明なidはプレーンテキストとして描画されます。
string–
filenameヘッダーに表示され、ダウンロードにも使われます。
string–
streamingハイライトを増分にし、新しい行に追従し、アクションを無効にします。
booleanfalse
diffコードを統合差分として扱います。
booleanfalse
lineNumbers行番号を表示します。差分では、falseにしない限り表示されます。
boolean–
startLine最初の行の番号。
number1
highlightマークする行番号。
number[][]
defaultWrap長い行を折り返した状態で開始します。
booleanfalse
collapseAfterこの行数より長いと折りたたみます。0なら折りたたみません。
number16
actionsヘッダー内の、組み込みのコントロールの前に置く追加のコントロール。
ReactNode–
onApplyApplyボタンを表示し、押すと「Applied」で確認されます。
() => unknown–
review保留中はレビューバーを表示し、承認または却下されるとバッジを表示します。
CodeBlockReview–
プロパティ型デフォルト
status現在の判断。
"pending" | "accepted" | "rejected"–
onAcceptAcceptまたは⌘↵から呼ばれます。
() => void–
onRejectRejectまたは⌘⌫から呼ばれます。
() => void–

CodeFence

Streamdownに登録します: plugins={{ renderers: [{ language: codeFenceLanguages, component: CodeFence }] }}。

プロパティ型デフォルト
codeフェンスの内容。Streamdownから渡されます。
string–
languageフェンスの言語。Streamdownから渡されます。
string–
meta言語の後ろにあるすべて: title="…"、{1,3-5}、showLineNumbers、startLine=10。
string–
isIncompleteフェンスがまだ開いている間はtrue。
boolean–
プロパティ型デフォルト
command実行されたコマンド。
string–
outputこれまでの出力。ストリーミングに合わせて追記します。
string""
runningスピナーを表示し、新しい出力に追従します。
booleanfalse
exitCode完了時に表示されます。0以外は失敗としてマークされます。
number–
titleヘッダーのラベル。
string"Terminal"
キーアクション
Tabヘッダーのアクション、コード領域、Show allの順に移動します。
⌘↵ブロック内にフォーカスがあるとき、保留中のレビューを承認します。
⌘⌫ブロック内にフォーカスがあるとき、保留中のレビューを却下します。
←→コード領域にフォーカスがあるとき、長い行をスクロールします。
  • コード領域は、「app/page.tsx code」のようにファイル名にちなんだ名前を持つフォーカス可能なリージョンで、キーボードユーザーがスクロールできます。
  • 変更された行は色だけに頼りません。+と−のマーカーは視覚的なもので、スクリーンリーダーは変更された各行の前に「added」または「removed」と読み上げます。
  • Copyは「Copied」を読み上げ、すべてのアイコンボタンにはラベルとツールチップがあります。
  • レビューのショートカットは、フィールドへの入力中は無視されるため、キー入力を奪うことはありません。

使用技術

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

コード

9 個のファイルを components/blocks/code-block に追加しました。