import { memo, useState, useContext } from 'react';
import type { CitationProps } from './types';
import { SourceHovercard, FaviconImage, getCleanDomain } from '~/components/Web/SourceHovercard';
import { CitationContext, useCitation, useCompositeCitations } from './Context';
import { useLocalize } from '~/hooks';
interface CompositeCitationProps {
citationId?: string;
node?: {
properties?: CitationProps;
};
}
export function CompositeCitation(props: CompositeCitationProps) {
const localize = useLocalize();
const { citations, citationId } = props.node?.properties ?? ({} as CitationProps);
const { setHoveredCitationId } = useContext(CitationContext);
const [currentPage, setCurrentPage] = useState(0);
const sources = useCompositeCitations(citations || []);
if (!sources || sources.length === 0) return null;
const totalPages = sources.length;
const getCitationLabel = () => {
if (!sources || sources.length === 0) return localize('com_citation_source');
const firstSource = sources[0];
const remainingCount = sources.length - 1;
const attribution =
firstSource.attribution ||
firstSource.title ||
getCleanDomain(firstSource.link || '') ||
localize('com_citation_source');
return remainingCount > 0 ? `${attribution} +${remainingCount}` : attribution;
};
const handlePrevPage = (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
if (currentPage > 0) {
setCurrentPage(currentPage - 1);
}
};
const handleNextPage = (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
if (currentPage < totalPages - 1) {
setCurrentPage(currentPage + 1);
}
};
const currentSource = sources?.[currentPage];
return (
setHoveredCitationId(citationId || null)}
onMouseLeave={() => setHoveredCitationId(null)}
>
{totalPages > 1 && (
{currentPage + 1}/{totalPages}
)}
{currentSource.attribution}
{currentSource.title}
{currentSource.snippet}
);
}
interface CitationComponentProps {
citationId: string;
citationType: 'span' | 'standalone' | 'composite' | 'group' | 'navlist';
node?: {
properties?: CitationProps;
};
}
export function Citation(props: CitationComponentProps) {
const localize = useLocalize();
const { citation, citationId } = props.node?.properties ?? {};
const { setHoveredCitationId } = useContext(CitationContext);
const refData = useCitation({
turn: citation?.turn || 0,
refType: citation?.refType,
index: citation?.index || 0,
});
if (!refData) return null;
const getCitationLabel = () => {
return (
refData.attribution ||
refData.title ||
getCleanDomain(refData.link || '') ||
localize('com_citation_source')
);
};
return (
setHoveredCitationId(citationId || null)}
onMouseLeave={() => setHoveredCitationId(null)}
/>
);
}
export interface HighlightedTextProps {
children: React.ReactNode;
citationId?: string;
}
export function useHighlightState(citationId: string | undefined) {
const { hoveredCitationId } = useContext(CitationContext);
return citationId && hoveredCitationId === citationId;
}
export const HighlightedText = memo(function HighlightedText({
children,
citationId,
}: HighlightedTextProps) {
const isHighlighted = useHighlightState(citationId);
return (
{children}
);
});