import React from 'react'; import { Box } from '@mui/material'; import { useTheme } from '@mui/material/styles'; import { SxProps, Theme } from '@mui/material'; import Accordion from '@mui/material/Accordion'; import AccordionSummary from '@mui/material/AccordionSummary'; import AccordionDetails from '@mui/material/AccordionDetails'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import LocationSearchingIcon from '@mui/icons-material/LocationSearching'; import { MessageRoles } from './Message'; import { ErrorOutline, InfoOutline, Memory, Psychology, /* Stream, */ } from '@mui/icons-material'; interface ChatBubbleProps { role: MessageRoles, isInfo?: boolean; children: React.ReactNode; sx?: SxProps; className?: string; title?: string; expanded?: boolean; expandable?: boolean; onExpand?: (open: boolean) => void; } function ChatBubble(props: ChatBubbleProps) { const { role, children, sx, className, title, onExpand, expandable, expanded } = props; const theme = useTheme(); const defaultRadius = '16px'; const defaultStyle = { padding: theme.spacing(1, 2), fontSize: '0.875rem', alignSelf: 'flex-start', maxWidth: '100%', minWidth: '100%', height: 'fit-content', '& > *': { color: 'inherit', overflow: 'hidden', m: 0, }, '& > :last-child': { mb: 0, m: 0, p: 0, }, }; const styles: any = { assistant: { ...defaultStyle, backgroundColor: theme.palette.primary.main, border: `1px solid ${theme.palette.secondary.main}`, borderRadius: `${defaultRadius} ${defaultRadius} ${defaultRadius} 0`, color: theme.palette.primary.contrastText, }, content: { ...defaultStyle, backgroundColor: '#F5F2EA', border: `1px solid ${theme.palette.custom.highlight}`, borderRadius: 0, alignSelf: 'center', color: theme.palette.text.primary, padding: '8px 8px', marginBottom: '0px', boxShadow: '0 2px 4px rgba(0, 0, 0, 0.05)', fontSize: '0.9rem', lineHeight: '1.3', fontFamily: theme.typography.fontFamily, }, error: { ...defaultStyle, backgroundColor: '#F8E7E7', border: `1px solid #D83A3A`, borderRadius: defaultRadius, maxWidth: '90%', minWidth: '90%', alignSelf: 'center', color: '#8B2525', padding: '10px 16px', boxShadow: '0 1px 3px rgba(216, 58, 58, 0.15)', }, 'fact-check': 'qualifications', 'job-description': 'content', 'job-requirements': 'qualifications', info: { ...defaultStyle, backgroundColor: '#BFD8D8', border: `1px solid ${theme.palette.secondary.main}`, borderRadius: defaultRadius, color: theme.palette.text.primary, opacity: 0.95, }, processing: 'status', qualifications: { ...defaultStyle, backgroundColor: theme.palette.primary.light, border: `1px solid ${theme.palette.secondary.main}`, borderRadius: `${defaultRadius} ${defaultRadius} ${defaultRadius} 0`, color: theme.palette.primary.contrastText, }, resume: 'content', searching: 'status', status: { ...defaultStyle, backgroundColor: 'rgba(74, 122, 125, 0.15)', border: `1px solid ${theme.palette.secondary.light}`, borderRadius: '4px', maxWidth: '75%', minWidth: '75%', alignSelf: 'center', color: theme.palette.secondary.dark, fontWeight: 500, fontSize: '0.95rem', padding: '8px 12px', opacity: 0.9, transition: 'opacity 0.3s ease-in-out', }, streaming: 'assistant', system: { ...defaultStyle, backgroundColor: '#EDEAE0', border: `1px dashed ${theme.palette.custom.highlight}`, borderRadius: defaultRadius, maxWidth: '90%', minWidth: '90%', alignSelf: 'center', color: theme.palette.text.primary, fontStyle: 'italic', }, thinking: 'status', user: { ...defaultStyle, backgroundColor: theme.palette.background.default, border: `1px solid ${theme.palette.custom.highlight}`, borderRadius: `${defaultRadius} ${defaultRadius} 0 ${defaultRadius}`, alignSelf: 'flex-end', color: theme.palette.primary.main, }, }; // Resolve string references in styles for (const [key, value] of Object.entries(styles)) { if (typeof value === 'string') { styles[key] = styles[value]; } } const icons: any = { error: , info: , processing: , searching: , thinking: , tooling: , }; // Render Accordion for expandable content if (expandable || title) { // Determine if Accordion is controlled const isControlled = typeof expanded === 'boolean' && typeof onExpand === 'function'; return ( { if (isControlled && onExpand) { onExpand(newExpanded); // Call onExpand with new state } }} sx={{ ...styles[role], ...sx }} > } slotProps={{ content: { sx: { fontWeight: 'bold', fontSize: '1.1rem', m: 0, p: 0, display: 'flex', justifyItems: 'center', }, }, }} > {title || ''} {children} ); } // Render non-expandable content return ( {icons[role] !== undefined && icons[role]} {children} ); } export type { ChatBubbleProps }; export { ChatBubble };