import { useState } from 'react'; import { MessageSquare, UserCircle, Bot, Star, User, Image as ImageIcon } from 'lucide-react'; import { cn } from '@/lib/utils'; import { Persona } from '@/types/persona'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { getPersonaAvatarSrc } from '@/utils/avatarUtils'; import { parseMentions, formatMentionsForDisplay } from '@/utils/mentionUtils'; import { focusGroupsApi } from '@/lib/api'; interface Message { id: string; senderId: string; // 'moderator' = AI Moderator, 'facilitator' = Human Facilitator, or participant ID text: string; timestamp: Date; type: 'question' | 'response' | 'system' | 'highlight'; highlighted?: boolean; } interface ChatMessageProps { message: Message; persona: Persona | null; toggleHighlight: () => void; participants?: Persona[]; // For parsing @mentions in message text focusGroupId?: string; // For loading creative assets } const ChatMessage = ({ message, persona, toggleHighlight, participants = [], focusGroupId }: ChatMessageProps) => { const [isHovered, setIsHovered] = useState(false); const isModerator = message.senderId === 'moderator'; const isFacilitator = message.senderId === 'facilitator'; // Parse and format mentions in the message text const parsedMentions = parseMentions(message.text, participants); const formattedText = formatMentionsForDisplay(message.text, parsedMentions.mentions); // Extract creative asset filename from message text if this is a creative review const extractAssetFilename = (text: string): string | null => { // Look for patterns like "asset: filename.jpg" or similar const patterns = [ // Match quoted filenames (most specific pattern first) /titled\s+['"]([^'"]+\.(jpg|jpeg|png))['\"]/i, // "titled 'filename.jpg'" /asset\s+['"]([^'"]+\.(jpg|jpeg|png))['\"]/i, // "asset 'filename.jpg'" /image\s+['"]([^'"]+\.(jpg|jpeg|png))['\"]/i, // "image 'filename.jpg'" /['"]([a-zA-Z0-9_\-]+\.(jpg|jpeg|png))['\"]/i, // Any quoted filename // Match focus group asset pattern without quotes /(fg-[a-f0-9]+-[a-f0-9]{32}\.(jpg|jpeg|png))/i, // fg-{id}-{uuid}.{ext} ]; for (const pattern of patterns) { const match = text.match(pattern); if (match) { return match[1]; } } return null; }; const assetFilename = extractAssetFilename(message.text); const hasCreativeAsset = (isModerator || isFacilitator) && assetFilename && focusGroupId; const handleToggleHighlight = () => { toggleHighlight(); }; return (
setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} data-highlighted={message.highlighted ? "true" : "false"} >
{isModerator ? (
) : isFacilitator ? (
) : persona ? (
{`${persona.name}
) : (
)}
{isModerator ? 'AI Moderator' : isFacilitator ? 'Human Facilitator' : persona?.name || 'Unknown'} {!isModerator && !isFacilitator && persona && ( {persona.occupation} )} {message.timestamp.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}

{formattedText}

{/* Display creative asset if this is a moderator message with an asset */} {hasCreativeAsset && (
Creative Asset
Creative asset for review { console.error('Failed to load creative asset:', focusGroupsApi.getAssetUrl(focusGroupId!, assetFilename!)); e.currentTarget.style.display = 'none'; // Show placeholder on error const placeholder = document.createElement('div'); placeholder.className = 'text-xs text-slate-500 italic p-2 border rounded bg-slate-100'; placeholder.textContent = `Creative asset not found: ${assetFilename}`; e.currentTarget.parentNode?.appendChild(placeholder); }} />
)}
); }; export default ChatMessage;