import React, { useState } from 'react'; import { Paperclip, AudioLines } from 'lucide-react'; import { MessageChainComponent, Image as ImageComponent, Plain, At, Voice, Quote, } from '@/app/infra/entities/message'; import ImagePreviewDialog from '@/app/home/pipelines/components/debug-dialog/ImagePreviewDialog'; interface MessageContentRendererProps { content: string; maxLines?: number; } export function MessageContentRenderer({ content, maxLines = 3, }: MessageContentRendererProps) { const [previewImageUrl, setPreviewImageUrl] = useState(''); const [showImagePreview, setShowImagePreview] = useState(false); // Try to parse content as message_chain JSON const parseContent = (content: string): MessageChainComponent[] | null => { try { const parsed = JSON.parse(content); if (Array.isArray(parsed) && parsed.length > 0 && parsed[0].type) { return parsed as MessageChainComponent[]; } return null; } catch { return null; } }; const renderMessageComponent = ( component: MessageChainComponent, index: number, ) => { switch (component.type) { case 'Plain': return {(component as Plain).text}; case 'At': { const atComponent = component as At; const displayName = atComponent.display || atComponent.target?.toString() || ''; return ( @{displayName} ); } case 'AtAll': return ( @All ); case 'Image': { const img = component as ImageComponent; const imageUrl = img.url || (img.base64 ? img.base64 : ''); if (!imageUrl) { return ( [Image] ); } return ( Message attachment { e.stopPropagation(); setPreviewImageUrl(imageUrl); setShowImagePreview(true); }} /> ); } case 'File': { const file = component as MessageChainComponent & { name?: string }; return ( {file.name || 'File'} ); } case 'Voice': { const voice = component as Voice; return ( Voice{voice.length ? ` ${voice.length}s` : ''} ); } case 'Quote': { const quote = component as Quote; return ( {quote.origin ?.filter((c) => (c as MessageChainComponent).type === 'Plain') .map((c) => (c as MessageChainComponent as Plain).text) .join('') || '[Quote]'} ); } case 'Source': return null; default: return ( [{component.type}] ); } }; const messageChain = parseContent(content); // Determine line clamp class const lineClampClass = maxLines === 2 ? 'line-clamp-2' : maxLines === 3 ? 'line-clamp-3' : maxLines === 4 ? 'line-clamp-4' : ''; if (messageChain) { // Filter out Source components as they render to null const visibleComponents = messageChain.filter( (component) => component.type !== 'Source', ); // If no visible components, show placeholder if (visibleComponents.length === 0) { return ( [Empty message] ); } // Render as message chain return ( <>
{messageChain.map((component, index) => renderMessageComponent(component, index), )}
setShowImagePreview(false)} /> ); } // Handle empty plain text if ( !content || content.trim() === '' || content === '[]' || content === '""' ) { return ( [Empty message] ); } // Render as plain text return {content}; }