Code Block

Codeblöcke für KI-Antworten. Syntaxhervorhebung, die mit dem Streaming Schritt hält, Kopieren, Herunterladen und Umbrechen, Zeilennummern und hervorgehobene Zeilen, Diffs mit Annehmen und Ablehnen sowie ein Terminal für Befehle.

Code in einer KI-Antwort kommt wenige Zeichen auf einmal an, oft als Diff, den jemand freigeben muss. Code Block hebt hervor, während er streamt, folgt neuen Zeilen, solange der Leser nicht nach oben gescrollt hat, und hält Kopieren, Herunterladen und Umbrechen aus dem Weg, bis der Code vollständig ist.

Die Hervorhebung nutzt Shiki mit den hellen und dunklen Themes von GitHub, pro Sprache bei Bedarf geladen. Beim Eintreffen von Text werden nur neue Zeilen tokenisiert, sodass lange Dateien schnell bleiben, und beide Themes werden gleichzeitig gerendert, sodass der Wechsel des Farbschemas nie aufblitzt. Blöcke mit mehr als 16 Zeilen falten sich hinter „Show all“.

Übergib einen Unified Diff, und du erhältst alte und neue Zeilennummern, eine Änderungszahl und getönte Zeilen; Kopieren liefert die neue Version, nicht den Diff. Füge ein Review hinzu, und der Leser kann die Änderung annehmen oder ablehnen, mit ⌘↵ und ⌘⌫, solange der Block den Fokus hat. CodeFence steckt denselben Block in Streamdown, und CodeTerminal zeigt Befehle mit ihrer Ausgabe und ihrem Exit-Code.

  1. Die Pro-Registry zu components.json hinzufügen

    components.json
    {
      "registries": {
        "@hextaui-pro": {
          "url": "https://hextaui.com/r/pro/{name}.json",
          "headers": {
            "Authorization": "Bearer ${HEXTAUI_PRO_TOKEN}"
          }
        }
      }
    }
  2. Token hinzufügen

    Erstelle auf deiner Kontoseite einen Token und trage ihn in .env.local als HEXTAUI_PRO_TOKEN ein.

  3. Den Block hinzufügen

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

In Markdown mit Streamdown

Registriere CodeFence als Renderer. Er liest die Sprache und Fence-Einstellungen wie title="app/page.tsx", {2,4-6} und showLineNumbers und hält die Hervorhebung aufrecht, solange der Fence noch streamt.

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

Eine Änderung prüfen

Zeige eine vorgeschlagene Änderung als Diff und lass den Leser sie annehmen oder ablehnen, mit ⌘↵ und ⌘⌫, solange der Block den Fokus hat.

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

Allein

Übergib Code und Sprache direkt, zum Beispiel wenn ein Tool-Aufruf Dateiinhalt zurückgibt.

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

Aufbau

Die Teile, die du zusammensetzt, von außen nach innen.

PartBeschreibung
CodeBlockDer Block selbst: Header, Aktionen, Code und die optionale Review-Leiste.
CodeFenceEin Streamdown-Renderer, der Fenced Code in einen CodeBlock verwandelt.
CodeTerminalEin Befehl mit seiner streamenden Ausgabe und seinem Exit-Status.
CopyButton, DownloadButton, WrapToggleDie Header-Aktionen, für deine eigenen Header exportiert.
LanguageIconDie Sprachmarke im Header.
PropTypStandard
codeDer Quelltext, oder ein Unified Diff, wenn diff gesetzt ist.
string–
languageShiki-Sprach-ID oder Alias, etwa tsx, py oder bash. Unbekannte IDs werden als reiner Text gerendert.
string–
filenameWird im Header angezeigt und für Downloads verwendet.
string–
streamingHält die Hervorhebung inkrementell, folgt neuen Zeilen und deaktiviert die Aktionen.
booleanfalse
diffBehandelt code als Unified Diff.
booleanfalse
lineNumbersZeigt Zeilennummern. Diffs zeigen sie, sofern nicht auf false gesetzt.
boolean–
startLineNummer der ersten Zeile.
number1
highlightZu markierende Zeilennummern.
number[][]
defaultWrapStartet mit umgebrochenen langen Zeilen.
booleanfalse
collapseAfterKlappt ein, wenn länger als diese Zeilenzahl. 0 klappt nie ein.
number16
actionsZusätzliche Steuerelemente im Header, vor den eingebauten.
ReactNode–
onApplyZeigt einen Apply-Button, der mit „Applied“ bestätigt.
() => unknown–
reviewZeigt die Review-Leiste, solange offen, und ein Badge, sobald angenommen oder abgelehnt.
CodeBlockReview–
PropTypStandard
statusDie aktuelle Entscheidung.
"pending" | "accepted" | "rejected"–
onAcceptWird von Accept oder ⌘↵ aufgerufen.
() => void–
onRejectWird von Reject oder ⌘⌫ aufgerufen.
() => void–

CodeFence

Mit Streamdown registrieren: plugins={{ renderers: [{ language: codeFenceLanguages, component: CodeFence }] }}.

PropTypStandard
codeInhalt des Fence, von Streamdown.
string–
languageSprache des Fence, von Streamdown.
string–
metaAlles nach der Sprache: title="…", {1,3-5}, showLineNumbers, startLine=10.
string–
isIncompleteTrue, solange der Fence noch offen ist.
boolean–
PropTypStandard
commandDer Befehl, der ausgeführt wurde.
string–
outputBisherige Ausgabe. Hänge beim Streamen an.
string""
runningZeigt einen Spinner und folgt neuer Ausgabe.
booleanfalse
exitCodeWird nach Abschluss angezeigt. Alles außer 0 gilt als fehlgeschlagen.
number–
titleHeader-Label.
string"Terminal"
TasteAktion
TabWechselt durch die Header-Aktionen, den Codebereich und Show all.
⌘↵Nimmt ein ausstehendes Review an, solange der Fokus im Block liegt.
⌘⌫Lehnt ein ausstehendes Review ab, solange der Fokus im Block liegt.
←→Scrollt lange Zeilen, wenn der Codebereich den Fokus hat.
  • Der Codebereich ist eine fokussierbare Region, benannt nach der Datei, zum Beispiel „app/page.tsx code“, damit Tastaturnutzer ihn scrollen können.
  • Geänderte Zeilen verlassen sich nicht auf Farbe. Die Markierungen + und − sind nur visuell, und Screenreader hören stattdessen vor jeder geänderten Zeile „hinzugefügt“ oder „entfernt“.
  • Copy sagt „Kopiert“ an, und jeder Icon-Button hat ein Label und einen Tooltip.
  • Review-Kürzel werden ignoriert, solange in einem Feld getippt wird, sodass sie nie Tastenanschläge stehlen.

Gebaut mit

Die kostenlosen HextaUI-Komponenten, aus denen Code Block besteht. Jede lässt sich einzeln installieren.

Code

9 Dateien, hinzugefügt zu components/blocks/code-block.