代码块

为代码块提供语法高亮、行号和复制到剪贴板功能。

CodeBlock 组件为代码块提供语法高亮、行号和复制到剪贴板功能。

使用 CLI 安装

AI Elements Vue
shadcn-vue CLI
npx ai-elements-vue@latest add code-block

手动安装

将以下文件复制并粘贴到同一文件夹中。

CodeBlock.vue
CodeBlockCopyButton.vue
context.ts
utils.ts
index.ts
<script setup lang="ts">
import type { BundledLanguage } from 'shiki'
import type { HTMLAttributes } from 'vue'
import { cn } from '@repo/shadcn-vue/lib/utils'
import { reactiveOmit } from '@vueuse/core'
import { computed, onBeforeUnmount, provide, ref, watch } from 'vue'
import { CodeBlockKey } from './context'
import { highlightCode } from './utils'

const props = withDefaults(
  defineProps<{
    code: string
    language: BundledLanguage
    showLineNumbers?: boolean
    class?: HTMLAttributes['class']
  }>(),
  {
    showLineNumbers: false,
  },
)

const delegatedProps = reactiveOmit(props, 'code', 'language', 'showLineNumbers', 'class')

const html = ref('')
const darkHtml = ref('')

provide(CodeBlockKey, {
  code: computed(() => props.code),
})

let requestId = 0
let isUnmounted = false

watch(
  () => [props.code, props.language, props.showLineNumbers] as const,
  async ([code, language, showLineNumbers]) => {
    requestId += 1
    const currentId = requestId

    try {
      const [light, dark] = await highlightCode(code, language, showLineNumbers)

      if (currentId === requestId && !isUnmounted) {
        html.value = light
        darkHtml.value = dark
      }
    }
    catch (error) {
      console.error('[CodeBlock] highlight failed', error)
    }
  },
  { immediate: true },
)

onBeforeUnmount(() => {
  isUnmounted = true
})
</script>

<template>
  <div
    data-slot="code-block"
    v-bind="delegatedProps"
    :class="cn('group relative w-full overflow-hidden rounded-md border bg-background text-foreground', props.class)"
  >
    <div class="relative">
      <div
        class="overflow-hidden dark:hidden [&>pre]:m-0 [&>pre]:bg-background! [&>pre]:p-4 [&>pre]:text-foreground! [&>pre]:text-sm [&_code]:font-mono [&_code]:text-sm"
        v-html="html"
      />
      <div
        class="hidden overflow-hidden dark:block [&>pre]:m-0 [&>pre]:bg-background! [&>pre]:p-4 [&>pre]:text-foreground! [&>pre]:text-sm [&_code]:font-mono [&_code]:text-sm"
        v-html="darkHtml"
      />
      <div v-if="$slots.default" class="absolute top-2 right-2 flex items-center gap-2">
        <slot />
      </div>
    </div>
  </div>
</template>

与 AI SDK 一起使用

使用 experimental_useObject hook 构建简单的代码生成工具。

将以下组件添加到你的前端:

app/page.vue
<script setup lang="ts">
import { experimental_useObject as useObject } from '@ai-sdk/vue'
import { ref } from 'vue'
import { z } from 'zod'
import { CodeBlock, CodeBlockCopyButton } from '@/components/ai-elements/code-block'
import { PromptInput, PromptInputSubmit, PromptInputTextarea } from '@/components/ai-elements/prompt-input'

const codeBlockSchema = z.object({
  language: z.string(),
  filename: z.string(),
  code: z.string(),
})

const input = ref('')
const { object, submit, isLoading } = useObject({
  api: '/api/codegen',
  schema: codeBlockSchema,
})

function handleSubmit(e: Event) {
  e.preventDefault()
  if (input.value.trim()) {
    submit(input.value)
  }
}
</script>

<template>
  <div class="max-w-4xl mx-auto p-6 relative size-full rounded-lg border h-[600px]">
    <div class="flex flex-col h-full">
      <div class="flex-1 overflow-auto mb-4">
        <CodeBlock
          v-if="object?.code && object?.language"
          :code="object.code"
          :language="object.language"
          :show-line-numbers="true"
        >
          <CodeBlockCopyButton />
        </CodeBlock>
      </div>

      <PromptInput
        class="mt-4 w-full max-w-2xl mx-auto relative"
        @submit="handleSubmit"
      >
        <PromptInputTextarea
          v-model="input"
          placeholder="Generate a Vue todolist component"
          class="pr-12"
        />
        <PromptInputSubmit
          :status="isLoading ? 'streaming' : 'ready'"
          :disabled="!input.trim()"
          class="absolute bottom-1 right-1"
        />
      </PromptInput>
    </div>
  </div>
</template>

将以下路由添加到你的后端:

server/api/codegen.post.ts
import { openai } from '@ai-sdk/openai'
import { streamObject } from 'ai'
import { z } from 'zod'

const codeBlockSchema = z.object({
  language: z.string(),
  filename: z.string(),
  code: z.string(),
})

export default defineEventHandler(async (event) => {
  const { prompt } = await readBody(event)

  const result = streamObject({
    model: openai('gpt-4o'),
    schema: codeBlockSchema,
    prompt:
      `You are a helpful coding assitant. Only generate code, no markdown formatting or backticks, or text.${
        prompt}`,
  })

  return result.toTextStreamResponse()
})

特性

  • 使用 Shiki 进行语法高亮
  • 行号(可选)
  • 复制到剪贴板功能
  • 自动浅色/深色主题切换
  • 可自定义样式
  • 可访问的设计

示例

深色模式

要在深色模式中使用 CodeBlock 组件,可以将其包装在带有 dark 类的 div 中。

Props

<CodeBlock />

coderequiredstring
要显示的代码内容。
languagerequiredBundledLanguage
用于语法高亮的编程语言。
showLineNumbersboolean
是否显示行号。
classstring
应用于根容器的额外 CSS 类。

<CodeBlockCopyButton />

timeoutnumber
显示已复制状态的时间(毫秒)。
classstring
应用于按钮的额外 CSS 类。
@copy() => void
成功复制后触发的回调。
@error(error: Error) => void
复制失败时触发的回调。