内联引用

一个可悬停的引用组件,在文本中内联显示来源信息和引用,非常适合带有引用的 AI 生成内容。

InlineCitation 组件提供了一种在文本内容中内联显示引用的方法,类似于学术论文或研究文档。它由一个引用标签组成,悬停时显示详细的来源信息,非常适合需要引用来源的 AI 生成内容。

Install using CLI

AI Elements Vue
shadcn-vue CLI
npx ai-elements-vue@latest add inline-citation

Install Manually

Copy and paste the following files into the same folder.

InlineCitation.vue
InlineCitationCard.vue
InlineCitationCardBody.vue
InlineCitationCardTrigger.vue
InlineCitationCarousel.vue
InlineCitationCarouselContent.vue
InlineCitationCarouselHeader.vue
InlineCitationCarouselIndex.vue
InlineCitationCarouselItem.vue
InlineCitationCarouselNext.vue
InlineCitationCarouselPrev.vue
InlineCitationQuote.vue
InlineCitationSource.vue
InlineCitationText.vue
index.ts
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@repo/shadcn-vue/lib/utils'

const props = defineProps<{
  class?: HTMLAttributes['class']
}>()
</script>

<template>
  <span :class="cn('group inline items-center gap-1', props.class)">
    <slot />
  </span>
</template>

特性

  • 悬停交互以显示详细的引用信息
  • 轮播导航,用于多个引用,带有上一个/下一个控件
  • 实时索引跟踪,显示当前幻灯片位置(例如,"1/5")
  • 支持来源标题、URL 和描述
  • 可选的相关摘录引用块
  • 可组合架构,用于灵活的引用格式
  • 可访问的设计,具有适当的键盘导航
  • 与 AI 生成内容无缝集成
  • 简洁的视觉设计,不会中断阅读流程
  • 智能徽章显示,显示来源主机名和计数

与 AI SDK 一起使用

使用 experimental_generateObject 为 AI 生成内容构建引用。

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

pages/index.vue
<script setup lang="ts">
import { experimental_useObject as useObject } from '@ai-sdk/vue'
import { computed } from 'vue'
import { citationSchema } from '@/app/api/citation/route'
import {
  InlineCitation,
  InlineCitationCard,
  InlineCitationCardBody,
  InlineCitationCardTrigger,
  InlineCitationCarousel,
  InlineCitationCarouselContent,
  InlineCitationCarouselHeader,
  InlineCitationCarouselIndex,
  InlineCitationCarouselItem,
  InlineCitationCarouselNext,
  InlineCitationCarouselPrev,
  InlineCitationQuote,
  InlineCitationSource,
} from '@/components/ai-elements/inline-citation'

import { Button } from '@/components/ui/button'

const { object, submit, isLoading } = useObject({
  api: '/api/citation',
  schema: citationSchema,
})

function handleSubmit(topic: string) {
  submit({ prompt: topic })
}

// Parse the content string to separate text from citation markers (e.g., [1])
const parsedContent = computed(() => {
  const content = object.value?.content
  if (!content)
    return []

  // Split by the citation pattern [number]
  const parts = content.split(/(\[\d+\])/)

  return parts.map((part) => {
    const citationMatch = part.match(/\[(\d+)\]/)

    if (citationMatch) {
      const citationNumber = citationMatch[1]
      // Find the corresponding citation data
      const citation = object.value?.citations?.find(
        (c: any) => String(c.number) === citationNumber
      )

      if (citation) {
        return {
          type: 'citation',
          data: citation,
          key: `citation-${citationNumber}`,
        }
      }
    }

    return {
      type: 'text',
      content: part,
      key: `text-${nanoid()}`
    }
  })
})

// Simple helper for unique keys if nanoid isn't available
function nanoid() {
  return Math.random().toString(36).substring(7)
}
</script>

<template>
  <div class="max-w-4xl mx-auto p-6 space-y-6">
    <div class="flex gap-2 mb-6">
      <Button
        :disabled="isLoading"
        variant="outline"
        @click="handleSubmit('artificial intelligence')"
      >
        Generate AI Content
      </Button>
      <Button
        :disabled="isLoading"
        variant="outline"
        @click="handleSubmit('climate change')"
      >
        Generate Climate Content
      </Button>
    </div>

    <div v-if="isLoading && !object" class="text-muted-foreground">
      Generating content with citations...
    </div>

    <div v-if="object?.content" class="prose prose-sm max-w-none">
      <p class="leading-relaxed">
        <template v-for="(part, index) in parsedContent" :key="index">
          <span v-if="part.type === 'text'">{{ part.content }}</span>

          <InlineCitation v-else-if="part.type === 'citation' && part.data">
            <InlineCitationCard>
              <InlineCitationCardTrigger :sources="[part.data.url]" />
              <InlineCitationCardBody>
                <InlineCitationCarousel>
                  <InlineCitationCarouselHeader>
                    <InlineCitationCarouselPrev />
                    <InlineCitationCarouselNext />
                    <InlineCitationCarouselIndex />
                  </InlineCitationCarouselHeader>
                  <InlineCitationCarouselContent>
                    <InlineCitationCarouselItem>
                      <InlineCitationSource
                        :title="part.data.title"
                        :url="part.data.url"
                        :description="part.data.description"
                      />
                      <InlineCitationQuote v-if="part.data.quote">
                        {{ part.data.quote }}
                      </InlineCitationQuote>
                    </InlineCitationCarouselItem>
                  </InlineCitationCarouselContent>
                </InlineCitationCarousel>
              </InlineCitationCardBody>
            </InlineCitationCard>
          </InlineCitation>
        </template>
      </p>
    </div>
  </div>
</template>

Add the following route to your backend:

server/api/citation.ts
import { streamObject } from 'ai'
import { z } from 'zod'

export const citationSchema = z.object({
  content: z.string(),
  citations: z.array(
    z.object({
      number: z.string(),
      title: z.string(),
      url: z.string(),
      description: z.string().optional(),
      quote: z.string().optional(),
    })
  ),
})

// Allow streaming responses up to 30 seconds
export const maxDuration = 30

export default defineEventHandler(async (event) => {
  const body = await readBody<{ prompt: string }>(event)

  const result = streamObject({
    model: 'openai/gpt-4o',
    schema: citationSchema,
    prompt: `Generate a well-researched paragraph about ${body.prompt} with proper citations.

    Include:
    - A comprehensive paragraph with inline citations marked as [1], [2], etc.
    - 2-3 citations with realistic source information
    - Each citation should have a title, URL, and optional description/quote
    - Make the content informative and the sources credible

    Format citations as numbered references within the text.`,
  })

  return result.toTextStreamResponse()
})

目前,Streamdown 或 Response 组件没有官方支持内联引用。这是因为:

  • 没有好的 Markdown 语法用于内联引用
  • 语言模型不会自然地使用内联引用语法响应
  • AI SDK 没有内置的内联引用支持

潜在方法

虽然这些方法是假设性的,并且没有官方支持,但有两种概念性的方式可以让内联引用与 Streamdown 一起工作:

  1. 脚注转换:GitHub Flavored Markdown (GFM) 使用 [^1] 语法处理脚注。你可以假设删除默认的脚注渲染,而是将脚注转换为内联引用。
  2. 自定义 HTML 语法:你可以添加一个系统提示,指示模型使用特殊的 HTML 语法,如 <citation />,并将其作为自定义组件传递给 Streamdown。

这些方法需要自定义实现,目前不支持开箱即用。我们将在未来调查此用例的官方支持。

目前,推荐的方法是使用 experimental_useObject(如上面的使用示例所示)生成结构化引用数据,然后手动解析和渲染内联引用。

Props

<InlineCitation />

classstring
''
Additional CSS classes to apply to the component.

<InlineCitationText />

classstring
''
Additional CSS classes to apply to the component.

<InlineCitationCard />

closeDelaynumber
0
Delay in milliseconds before the hover card closes.
openDelaynumber
0
Delay in milliseconds before the hover card opens.

<InlineCitationCardTrigger />

sourcesstring[]
来源 URL 数组。第一个 URL 的主机名显示在徽章上,如果有多个来源,则显示计数指示器。
classstring
''
Additional CSS classes to apply to the component.

<InlineCitationCardBody />

classstring
''
Additional CSS classes to apply to the component.

<InlineCitationCarousel />

classstring
''
Additional CSS classes to apply to the component.

<InlineCitationCarouselContent />

classstring
''
Additional CSS classes to apply to the component.

<InlineCitationCarouselItem />

classstring
''
Additional CSS classes to apply to the component.

<InlineCitationCarouselHeader />

classstring
''
Additional CSS classes to apply to the component.

<InlineCitationCarouselIndex />

classstring
''
Additional CSS classes to apply to the component.

<InlineCitationCarouselPrev />

classstring
''
Additional CSS classes to apply to the component.

<InlineCitationCarouselNext />

classstring
''
Additional CSS classes to apply to the component.

<InlineCitationSource />

titlestring
引用来源的标题。
urlstring
引用来源的 URL。
descriptionstring
引用来源的简要描述。
classstring
''
Additional CSS classes to apply to the component.

<InlineCitationQuote />

classstring
''
Additional CSS classes to apply to the component.