Files
gh-nathanonn-claude-skills-…/docs/components/inline-citation.md
2025-11-30 08:41:51 +08:00

389 lines
12 KiB
Markdown

# Inline Citation
URL: /components/inline-citation
---
title: Inline Citation
description: A hoverable citation component that displays source information and quotes inline with text, perfect for AI-generated content with references.
path: elements/components/inline-citation
---
The `InlineCitation` component provides a way to display citations inline with text content, similar to academic papers or research documents. It consists of a citation pill that shows detailed source information on hover, making it perfect for AI-generated content that needs to reference sources.
<Preview path="inline-citation" />
## Installation
<ElementsInstaller path="inline-citation" />
## Usage
```tsx
import {
InlineCitation,
InlineCitationCard,
InlineCitationCardBody,
InlineCitationCardTrigger,
InlineCitationCarousel,
InlineCitationCarouselContent,
InlineCitationCarouselItem,
InlineCitationCarouselHeader,
InlineCitationCarouselIndex,
InlineCitationSource,
InlineCitationText,
} from "@/components/ai-elements/inline-citation";
```
```tsx
<InlineCitation>
<InlineCitationText>{citation.text}</InlineCitationText>
<InlineCitationCard>
<InlineCitationCardTrigger sources={citation.sources.map((source) => source.url)} />
<InlineCitationCardBody>
<InlineCitationCarousel>
<InlineCitationCarouselHeader>
<InlineCitationCarouselIndex />
</InlineCitationCarouselHeader>
<InlineCitationCarouselContent>
<InlineCitationCarouselItem>
<InlineCitationSource title="AI SDK" url="https://ai-sdk.dev" description="The AI Toolkit for TypeScript" />
</InlineCitationCarouselItem>
</InlineCitationCarouselContent>
</InlineCitationCarousel>
</InlineCitationCardBody>
</InlineCitationCard>
</InlineCitation>
```
## Usage with AI SDK
Build citations for AI-generated content using [`experimental_generateObject`](/docs/reference/ai-sdk-ui/use-object).
Add the following component to your frontend:
```tsx title="app/page.tsx"
"use client";
import { experimental_useObject as useObject } from "@ai-sdk/react";
import {
InlineCitation,
InlineCitationText,
InlineCitationCard,
InlineCitationCardTrigger,
InlineCitationCardBody,
InlineCitationCarousel,
InlineCitationCarouselContent,
InlineCitationCarouselItem,
InlineCitationCarouselHeader,
InlineCitationCarouselIndex,
InlineCitationCarouselPrev,
InlineCitationCarouselNext,
InlineCitationSource,
InlineCitationQuote,
} from "@/components/ai-elements/inline-citation";
import { Button } from "@/components/ui/button";
import { citationSchema } from "@/app/api/citation/route";
const CitationDemo = () => {
const { object, submit, isLoading } = useObject({
api: "/api/citation",
schema: citationSchema,
});
const handleSubmit = (topic: string) => {
submit({ prompt: topic });
};
return (
<div className="max-w-4xl mx-auto p-6 space-y-6">
<div className="flex gap-2 mb-6">
<Button onClick={() => handleSubmit("artificial intelligence")} disabled={isLoading} variant="outline">
Generate AI Content
</Button>
<Button onClick={() => handleSubmit("climate change")} disabled={isLoading} variant="outline">
Generate Climate Content
</Button>
</div>
{isLoading && !object && <div className="text-muted-foreground">Generating content with citations...</div>}
{object?.content && (
<div className="prose prose-sm max-w-none">
<p className="leading-relaxed">
{object.content.split(/(\[\d+\])/).map((part, index) => {
const citationMatch = part.match(/\[(\d+)\]/);
if (citationMatch) {
const citationNumber = citationMatch[1];
const citation = object.citations?.find((c: any) => c.number === citationNumber);
if (citation) {
return (
<InlineCitation key={index}>
<InlineCitationCard>
<InlineCitationCardTrigger sources={[citation.url]} />
<InlineCitationCardBody>
<InlineCitationCarousel>
<InlineCitationCarouselHeader>
<InlineCitationCarouselPrev />
<InlineCitationCarouselNext />
<InlineCitationCarouselIndex />
</InlineCitationCarouselHeader>
<InlineCitationCarouselContent>
<InlineCitationCarouselItem>
<InlineCitationSource
title={citation.title}
url={citation.url}
description={citation.description}
/>
{citation.quote && <InlineCitationQuote>{citation.quote}</InlineCitationQuote>}
</InlineCitationCarouselItem>
</InlineCitationCarouselContent>
</InlineCitationCarousel>
</InlineCitationCardBody>
</InlineCitationCard>
</InlineCitation>
);
}
}
return part;
})}
</p>
</div>
)}
</div>
);
};
export default CitationDemo;
```
Add the following route to your backend:
```ts title="app/api/citation/route.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 async function POST(req: Request) {
const { prompt } = await req.json();
const result = streamObject({
model: "openai/gpt-4o",
schema: citationSchema,
prompt: `Generate a well-researched paragraph about ${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();
}
```
## Features
- Hover interaction to reveal detailed citation information
- **Carousel navigation** for multiple citations with prev/next controls
- **Live index tracking** showing current slide position (e.g., "1/5")
- Support for source titles, URLs, and descriptions
- Optional quote blocks for relevant excerpts
- Composable architecture for flexible citation formats
- Accessible design with proper keyboard navigation
- Seamless integration with AI-generated content
- Clean visual design that doesn't disrupt reading flow
- Smart badge display showing source hostname and count
## Props
### `<InlineCitation />`
<TypeTable
type={{
'...props': {
description: 'Any other props are spread to the root span element.',
type: 'React.ComponentProps<"span">',
},
}}
/>
### `<InlineCitationText />`
<TypeTable
type={{
'...props': {
description: 'Any other props are spread to the underlying span element.',
type: 'React.ComponentProps<"span">',
},
}}
/>
### `<InlineCitationCard />`
<TypeTable
type={{
'...props': {
description: 'Any other props are spread to the HoverCard component.',
type: 'React.ComponentProps<"span">',
},
}}
/>
### `<InlineCitationCardTrigger />`
<TypeTable
type={{
sources: {
description: 'Array of source URLs. The length determines the number displayed in the badge.',
type: 'string[]',
},
'...props': {
description: 'Any other props are spread to the underlying button element.',
type: 'React.ComponentProps<"button">',
},
}}
/>
### `<InlineCitationCardBody />`
<TypeTable
type={{
'...props': {
description: 'Any other props are spread to the underlying div.',
type: 'React.ComponentProps<"div">',
},
}}
/>
### `<InlineCitationCarousel />`
<TypeTable
type={{
'...props': {
description: 'Any other props are spread to the underlying Carousel component.',
type: 'React.ComponentProps<typeof Carousel>',
},
}}
/>
### `<InlineCitationCarouselContent />`
<TypeTable
type={{
'...props': {
description: 'Any other props are spread to the underlying CarouselContent component.',
type: 'React.ComponentProps<"div">',
},
}}
/>
### `<InlineCitationCarouselItem />`
<TypeTable
type={{
'...props': {
description: 'Any other props are spread to the underlying div.',
type: 'React.ComponentProps<"div">',
},
}}
/>
### `<InlineCitationCarouselHeader />`
<TypeTable
type={{
'...props': {
description: 'Any other props are spread to the underlying div.',
type: 'React.ComponentProps<"div">',
},
}}
/>
### `<InlineCitationCarouselIndex />`
<TypeTable
type={{
'...props': {
description: 'Any other props are spread to the underlying div. Children will override the default index display.',
type: 'React.ComponentProps<"div">',
},
}}
/>
### `<InlineCitationCarouselPrev />`
<TypeTable
type={{
'...props': {
description: 'Any other props are spread to the underlying CarouselPrevious component.',
type: 'React.ComponentProps<typeof CarouselPrevious>',
},
}}
/>
### `<InlineCitationCarouselNext />`
<TypeTable
type={{
'...props': {
description: 'Any other props are spread to the underlying CarouselNext component.',
type: 'React.ComponentProps<typeof CarouselNext>',
},
}}
/>
### `<InlineCitationSource />`
<TypeTable
type={{
title: {
description: 'The title of the source.',
type: 'string',
},
url: {
description: 'The URL of the source.',
type: 'string',
},
description: {
description: 'A brief description of the source.',
type: 'string',
},
'...props': {
description: 'Any other props are spread to the underlying div.',
type: 'React.ComponentProps<"div">',
},
}}
/>
### `<InlineCitationQuote />`
<TypeTable
type={{
'...props': {
description: 'Any other props are spread to the underlying blockquote element.',
type: 'React.ComponentProps<"blockquote">',
},
}}
/>