import type { Message } from "@langchain/langgraph-sdk";
import { ExternalLinkIcon, LinkIcon } from "lucide-react";
import { useParams } from "next/navigation";
import { memo, useMemo } from "react";
import {
InlineCitationCard,
InlineCitationCardBody,
InlineCitationSource,
} from "@/components/ai-elements/inline-citation";
import {
Message as AIElementMessage,
MessageContent as AIElementMessageContent,
MessageResponse as AIElementMessageResponse,
MessageToolbar,
} from "@/components/ai-elements/message";
import { Badge } from "@/components/ui/badge";
import { HoverCardTrigger } from "@/components/ui/hover-card";
import { resolveArtifactURL } from "@/core/artifacts/utils";
import {
type Citation,
buildCitationMap,
extractDomainFromUrl,
parseCitations,
} from "@/core/citations";
import {
extractContentFromMessage,
extractReasoningContentFromMessage,
} from "@/core/messages/utils";
import { useRehypeSplitWordsIntoSpans } from "@/core/rehype";
import { cn } from "@/lib/utils";
import { CopyButton } from "../copy-button";
export function MessageListItem({
className,
message,
isLoading,
}: {
className?: string;
message: Message;
isLoading?: boolean;
}) {
return (
);
}
function MessageContent_({
className,
message,
isLoading = false,
}: {
className?: string;
message: Message;
isLoading?: boolean;
}) {
const rehypePlugins = useRehypeSplitWordsIntoSpans(isLoading);
// Extract and parse citations from message content
const { citations, cleanContent } = useMemo(() => {
const reasoningContent = extractReasoningContentFromMessage(message);
const rawContent = extractContentFromMessage(message);
if (!isLoading && reasoningContent && !rawContent) {
return { citations: [], cleanContent: reasoningContent };
}
return parseCitations(rawContent ?? "");
}, [isLoading, message]);
// Build citation map for quick URL lookup
const citationMap = useMemo(
() => buildCitationMap(citations),
[citations],
);
const { thread_id } = useParams<{ thread_id: string }>();
return (
{/* Citations list at the top */}
{citations.length > 0 && }
) => {
if (!href) {
return {children};
}
// Check if this link matches a citation
const citation = citationMap.get(href);
if (citation) {
return (
{children}
);
}
// Regular external link
return (
{children}
);
},
img: ({ src, alt }: React.ImgHTMLAttributes) => {
if (!src) return null;
if (typeof src !== "string") {
return (
);
}
let url = src;
if (src.startsWith("/mnt/")) {
url = resolveArtifactURL(src, thread_id);
}
return (
);
},
}}
>
{cleanContent}
);
}
/**
* Citations list component that displays all sources at the top
*/
function CitationsList({ citations }: { citations: Citation[] }) {
if (citations.length === 0) return null;
return (
Sources ({citations.length})
{citations.map((citation) => (
))}
);
}
/**
* Single citation badge in the citations list
*/
function CitationBadge({ citation }: { citation: Citation }) {
const domain = extractDomainFromUrl(citation.url);
return (
{domain}
);
}
/**
* Citation link component that renders as a hover card badge
*/
function CitationLink({
citation,
href,
children,
}: {
citation: Citation;
href: string;
children: React.ReactNode;
}) {
const domain = extractDomainFromUrl(href);
return (
e.stopPropagation()}
>
{children ?? domain}
);
}
const MessageContent = memo(MessageContent_);