"use client"; import type { ReactElement, ReactNode } from "react"; import { parsePromptMentionUrl, type PromptMentionSource, } from "@/lib/canvas/prompt-mentions"; import { cn } from "@/lib/utils"; export type PromptSegment = | { type: "text"; value: string } | { type: "mention"; source: PromptMentionSource }; type PromptMentionPreviewProps = { prompt: string; mentionSources: PromptMentionSource[]; className?: string; textClassName?: string; chipClassName?: string; variant?: "link" | "image"; placeholder?: ReactNode; }; export function PromptMentionPreview({ prompt, mentionSources, className, textClassName, chipClassName, variant = "link", placeholder, }: PromptMentionPreviewProps): ReactElement | null { const segments = splitPromptSegments(prompt, mentionSources); if (!segments.length) { return placeholder ? <>{placeholder} : null; } return (
{segments.map((segment, index) => { if (segment.type === "text") { return ( {segment.value} ); } if (variant === "image") { return ( {segment.source.imageUrl ? ( {segment.source.description} ) : null} {segment.source.label} ); } return ( {segment.source.label} ); })}
); } export function splitPromptSegments( prompt: string, mentionSources: PromptMentionSource[], ): PromptSegment[] { if (!prompt) { return []; } const sortedSources = [...mentionSources].sort( (left, right) => Math.max(right.token.length, right.alias.length) - Math.max(left.token.length, left.alias.length), ); const segments: PromptSegment[] = []; let cursor = 0; while (cursor < prompt.length) { const markdownMatch = parseMarkdownMentionAt(prompt, cursor); const matchedSource = markdownMatch ? sortedSources.find((source) => source.nodeId === markdownMatch.nodeId) : sortedSources.find((source) => prompt.startsWith(source.alias, cursor)); if (!matchedSource) { let nextCursor = cursor + 1; while (nextCursor < prompt.length) { const hasMentionAhead = Boolean(parseMarkdownMentionAt(prompt, nextCursor)) || sortedSources.some((source) => prompt.startsWith(source.alias, nextCursor)); if (hasMentionAhead) break; nextCursor += 1; } segments.push({ type: "text", value: prompt.slice(cursor, nextCursor), }); cursor = nextCursor; continue; } segments.push({ type: "mention", source: matchedSource, }); cursor += markdownMatch?.length ?? matchedSource.alias.length; } return segments; } function parseMarkdownMentionAt(prompt: string, cursor: number) { const match = /^\[([^\]]+)\]\(([^)]+)\)/.exec(prompt.slice(cursor)); if (!match) return null; const nodeId = parsePromptMentionUrl(match[2]); if (!nodeId) return null; return { label: match[1], nodeId, length: match[0].length, }; }