Code Block

Blocos de código feitos para respostas de IA. Realce de sintaxe que acompanha o streaming, copiar, baixar e quebra de linha, números de linha e linhas destacadas, diffs com aceitar e rejeitar e um terminal para comandos.

O código em uma resposta de IA chega alguns caracteres por vez, muitas vezes como um diff que alguém precisa aprovar. O Code Block destaca a sintaxe enquanto chega, acompanha as novas linhas a menos que o leitor tenha rolado para cima e mantém copiar, baixar e quebra de linha fora do caminho até o código estar completo.

O realce usa o Shiki com os temas claro e escuro do GitHub, carregados por linguagem sob demanda. Apenas as novas linhas são tokenizadas conforme o texto chega, então arquivos longos continuam rápidos, e os dois temas são renderizados ao mesmo tempo, então trocar o esquema de cores nunca causa um flash. Blocos com mais de 16 linhas ficam dobrados atrás de "Show all".

Passe um diff unificado e você obtém os números de linha antigos e novos, uma contagem de alterações e linhas tingidas; copiar fornece a versão nova, não o diff. Adicione uma revisão e o leitor pode aceitar ou rejeitar a alteração, com ⌘↵ e ⌘⌫ enquanto o bloco tem o foco. O CodeFence encaixa o mesmo bloco no Streamdown, e o CodeTerminal mostra comandos com sua saída e código de saída.

  1. Adicione o registro Pro ao components.json

    components.json
    {
      "registries": {
        "@hextaui-pro": {
          "url": "https://hextaui.com/r/pro/{name}.json",
          "headers": {
            "Authorization": "Bearer ${HEXTAUI_PRO_TOKEN}"
          }
        }
      }
    }
  2. Adicione seu token

    Crie um token na sua página de conta e coloque-o em .env.local como HEXTAUI_PRO_TOKEN.

  3. Adicione o bloco

    pnpm dlx shadcn@latest add @hextaui-pro/code-block

No Markdown com Streamdown

Registre o CodeFence como renderizador. Ele lê a linguagem e as configurações do fence como title="app/page.tsx", {2,4-6} e showLineNumbers, e mantém o realce enquanto o fence ainda está em streaming.

"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>
  )
}

Revisando uma alteração

Mostre uma edição proposta como um diff e deixe o leitor aceitá-la ou rejeitá-la, com ⌘↵ e ⌘⌫ enquanto o bloco tem o foco.

"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")
        },
      }}
    />
  )
}

Isolado

Passe o código e a linguagem diretamente, por exemplo quando uma chamada de ferramenta retorna o conteúdo de um arquivo.

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]}
    />
  )
}

Anatomia

As partes que você compõe, de fora para dentro.

ParteDescrição
CodeBlockO próprio bloco: cabeçalho, ações, código e a barra de revisão opcional.
CodeFenceUm renderizador do Streamdown que transforma código delimitado em um CodeBlock.
CodeTerminalUm comando com sua saída em streaming e o status de saída.
CopyButton, DownloadButton, WrapToggleAs ações do cabeçalho, exportadas para os seus próprios cabeçalhos.
LanguageIconA marca da linguagem usada no cabeçalho.
PropTipoPadrão
codeO código-fonte, ou um diff unificado quando diff está definido.
string–
languageId ou alias de linguagem do Shiki, como tsx, py ou bash. Ids desconhecidos são renderizados como texto simples.
string–
filenameExibido no cabeçalho e usado nos downloads.
string–
streamingMantém o realce incremental, acompanha as novas linhas e desabilita as ações.
booleanfalse
diffTrata o código como um diff unificado.
booleanfalse
lineNumbersMostra os números de linha. Os diffs os mostram, a menos que seja definido como false.
boolean–
startLineNúmero da primeira linha.
number1
highlightNúmeros das linhas a marcar.
number[][]
defaultWrapComeça com as linhas longas quebradas.
booleanfalse
collapseAfterRecolhe quando for maior que este número de linhas. 0 nunca recolhe.
number16
actionsControles extras no cabeçalho, antes dos nativos.
ReactNode–
onApplyMostra um botão Apply que confirma com "Applied".
() => unknown–
reviewMostra a barra de revisão enquanto pendente e um badge depois de aceita ou rejeitada.
CodeBlockReview–
PropTipoPadrão
statusA decisão atual.
"pending" | "accepted" | "rejected"–
onAcceptChamado por Accept ou ⌘↵.
() => void–
onRejectChamado por Reject ou ⌘⌫.
() => void–

CodeFence

Registre no Streamdown: plugins={{ renderers: [{ language: codeFenceLanguages, component: CodeFence }] }}.

PropTipoPadrão
codeConteúdo do fence, vindo do Streamdown.
string–
languageLinguagem do fence, vinda do Streamdown.
string–
metaTudo depois da linguagem: title="…", {1,3-5}, showLineNumbers, startLine=10.
string–
isIncompleteTrue enquanto o fence ainda está aberto.
boolean–
PropTipoPadrão
commandO comando que foi executado.
string–
outputA saída até agora. Acrescente conforme ela chega em streaming.
string""
runningMostra um spinner e acompanha a nova saída.
booleanfalse
exitCodeExibido ao terminar. Qualquer valor diferente de 0 é marcado como falha.
number–
titleRótulo do cabeçalho.
string"Terminal"
TeclaAção
TabPercorre as ações do cabeçalho, a área de código e o Show all.
⌘↵Aceita uma revisão pendente enquanto o foco está dentro do bloco.
⌘⌫Rejeita uma revisão pendente enquanto o foco está dentro do bloco.
←→Rola as linhas longas quando a área de código tem o foco.
  • A área de código é uma região focalizável com o nome do arquivo, por exemplo "código de app/page.tsx", para que quem usa o teclado possa rolá-la.
  • As linhas alteradas não dependem de cor. Os marcadores + e − são apenas visuais, e os leitores de tela ouvem "adicionada" ou "removida" antes de cada linha alterada.
  • Copy anuncia "Copiado" e todo botão de ícone tem um rótulo e um tooltip.
  • Os atalhos de revisão são ignorados enquanto você digita em um campo, então nunca roubam teclas.

Construído com

Os componentes gratuitos do HextaUI de que Code Block é feito. Cada um é instalado separadamente.

Código

9 arquivos, adicionados a components/blocks/code-block.