Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions components/citation-link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Link from 'next/link'

import type { SearchResultItem } from '@/lib/types'
import { cn } from '@/lib/utils'
import { isCitationLabel } from '@/lib/utils/citation'

import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import {
Expand Down Expand Up @@ -37,8 +38,7 @@ export const CitationLink = memo(function CitationLink({
}: CitationLinkProps) {
const [open, setOpen] = useState(false)
const childrenText = children?.toString() || ''
// Match domain names (alphanumeric and hyphens) or numbers for backward compatibility
const isCitation = /^[\w-]+$/.test(childrenText)
const isCitation = isCitationLabel(childrenText)

const linkClasses = cn(
isCitation
Expand Down
5 changes: 2 additions & 3 deletions components/custom-link.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { AnchorHTMLAttributes, DetailedHTMLProps } from 'react'

import type { SearchResultItem } from '@/lib/types'
import { cn } from '@/lib/utils'
import { isCitationLabel } from '@/lib/utils/citation'

import { useCitation } from './citation-context'
import { CitationLink } from './citation-link'
Expand All @@ -19,8 +19,7 @@ export function Citing({
}: CustomLinkProps) {
const { citationMaps } = useCitation()
const childrenText = children?.toString() || ''
// Match domain names (alphanumeric and hyphens) or numbers for backward compatibility
const isCitation = /^[\w-]+$/.test(childrenText)
const isCitation = isCitationLabel(childrenText)

// Get citation data if this is a citation
let citationData: SearchResultItem | undefined = undefined
Expand Down
42 changes: 41 additions & 1 deletion lib/utils/__tests__/citation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest'

import type { SearchResultItem } from '@/lib/types'

import { processCitations } from '../citation'
import { isCitationLabel, processCitations } from '../citation'

describe('processCitations', () => {
const mockCitationMaps = {
Expand Down Expand Up @@ -65,6 +65,30 @@ describe('processCitations', () => {
)
})

it('converts citations with dotted display labels', () => {
const citationMaps = {
toolCall1: {
1: {
title: 'Global News',
url: 'https://topics.global.example.com/portal/news/page.html',
content: 'News article'
},
2: {
title: 'World Report',
url: 'https://articles.world.example.net/articles/-/123',
content: 'News article'
}
} as Record<number, SearchResultItem>
}

const content = 'Sources [1](#toolCall1) [2](#toolCall1)'
const result = processCitations(content, citationMaps)

expect(result).toBe(
'Sources [global.example](https://topics.global.example.com/portal/news/page.html) [world.example](https://articles.world.example.net/articles/-/123)'
)
})

it('returns empty string for invalid citation numbers', () => {
const content = 'Invalid [999](#toolCall1) citation'
const result = processCitations(content, mockCitationMaps)
Expand Down Expand Up @@ -157,4 +181,20 @@ describe('processCitations', () => {
// -1 doesn't match the regex pattern \d+, so it remains unchanged
expect(result).toBe('Edge cases: [-1](#toolCall1)')
})

describe('isCitationLabel', () => {
it('accepts numeric, simple domain, and dotted domain labels', () => {
expect(isCitationLabel('1')).toBe(true)
expect(isCitationLabel('youtube')).toBe(true)
expect(isCitationLabel('global.example')).toBe(true)
expect(isCitationLabel('world.example')).toBe(true)
})

it('rejects punctuation and whitespace outside the label', () => {
expect(isCitationLabel('')).toBe(false)
expect(isCitationLabel('global.example.')).toBe(false)
expect(isCitationLabel('.global.example')).toBe(false)
expect(isCitationLabel('global example')).toBe(false)
})
})
})
4 changes: 4 additions & 0 deletions lib/utils/citation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ function isValidUrl(url: string): boolean {
}
}

export function isCitationLabel(label: string): boolean {
return /^[\w-]+(?:\.[\w-]+)*$/.test(label)
}

/**
* Extract citation maps from a message's tool parts
* Returns a map of toolCallId to citation map
Expand Down
Loading