import { useState, useRef } from 'react'; import Divider from '@mui/material/Divider'; import Accordion from '@mui/material/Accordion'; import AccordionSummary from '@mui/material/AccordionSummary'; import AccordionDetails from '@mui/material/AccordionDetails'; import Card from '@mui/material/Card'; import Table from '@mui/material/Table'; import TableBody from '@mui/material/TableBody'; import TableCell from '@mui/material/TableCell'; import TableContainer from '@mui/material/TableContainer'; import TableHead from '@mui/material/TableHead'; import TableRow from '@mui/material/TableRow'; import Button from '@mui/material/Button'; import CardContent from '@mui/material/CardContent'; import CardActions from '@mui/material/CardActions'; import Collapse from '@mui/material/Collapse'; import { ExpandMore } from './ExpandMore'; import JsonView from '@uiw/react-json-view'; import React from 'react'; import { Box } from '@mui/material'; import { useTheme } from '@mui/material/styles'; import { SxProps, Theme } from '@mui/material'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import LocationSearchingIcon from '@mui/icons-material/LocationSearching'; import { ErrorOutline, InfoOutline, Memory, Psychology, /* Stream, */ } from '@mui/icons-material'; import { StyledMarkdown } from './StyledMarkdown'; import { VectorVisualizer } from './VectorVisualizer'; import { SetSnackType } from './Snack'; import { CopyBubble } from './CopyBubble'; import { Scrollable } from './Scrollable'; import { BackstoryElementProps } from './BackstoryTab'; import { ChatMessage, ChatSession, ChatMessageMetaData, ChromaDBGetResponse, ApiActivityType, ChatMessageUser, ChatMessageError, ChatMessageStatus, ChatSenderType } from 'types/types'; const getStyle = (theme: Theme, type: ApiActivityType | ChatSenderType | "error"): any => { 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', generating: 'status', 'job-description': 'content', 'job-requirements': 'qualifications', information: { ...defaultStyle, backgroundColor: '#BFD8D8', border: `1px solid ${theme.palette.secondary.main}`, borderRadius: defaultRadius, color: theme.palette.text.primary, opacity: 0.95, }, preparing: 'status', 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: 'response', 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]; } } if (!(type in styles)) { console.log(`Style does not exist for: ${type}`); } return styles[type]; } const getIcon = (activityType: ApiActivityType | ChatSenderType | "error"): React.ReactNode | null => { const icons: any = { error: , generating: , information: , preparing: , processing: , system: , thinking: , tooling: , }; return icons[activityType] || null; } interface MessageProps extends BackstoryElementProps { message: ChatMessageUser | ChatMessage | ChatMessageError | ChatMessageStatus, title?: string, chatSession?: ChatSession, className?: string, sx?: SxProps, expandable?: boolean, expanded?: boolean, onExpand?: (open: boolean) => void, }; interface MessageMetaProps { metadata: ChatMessageMetaData, messageProps: MessageProps }; const MessageMeta = (props: MessageMetaProps) => { const { /* MessageData */ ragResults = [], tools = null, evalCount = 0, evalDuration = 0, promptEvalCount = 0, promptEvalDuration = 0, } = props.metadata || {}; const message: any = props.messageProps.message; let llm_submission: string = "<|system|>\n" llm_submission += message.system_prompt + "\n\n" llm_submission += message.context_prompt return (<> { promptEvalDuration !== 0 && evalDuration !== 0 && <> Tokens Time (s) TPS Prompt {promptEvalCount} {Math.round(promptEvalDuration / 10 ** 7) / 100} {Math.round(promptEvalCount * 10 ** 9 / promptEvalDuration)} Response {evalCount} {Math.round(evalDuration / 10 ** 7) / 100} {Math.round(evalCount * 10 ** 9 / evalDuration)} Total {promptEvalCount + evalCount} {Math.round((promptEvalDuration + evalDuration) / 10 ** 7) / 100} {Math.round((promptEvalCount + evalCount) * 10 ** 9 / (promptEvalDuration + evalDuration))}
} { tools && tools.tool_calls && tools.tool_calls.length !== 0 && }> Tools queried { tools.tool_calls.map((tool: any, index: number) => {index !== 0 && } {tool.name} {tool.content !== "null" && { if (typeof (children) === "string" && children.match("\n")) { return
{children}
} }} />
} {tool.content === "null" && "No response from tool call"}
) }
} { ragResults.map((collection: ChromaDBGetResponse) => ( }> Top {collection.ids?.length} RAG matches from {collection.size} entries using an embedding vector of {collection.queryEmbedding?.length} dimensions {/* { ...rag, query: message.prompt }} /> */} )) } }> Full Response Details Copy LLM submission: { if (typeof (children) === "string" && children.match("\n")) { return
{children.trim()}
} }} />
); }; interface MessageContainerProps { type: ApiActivityType | ChatSenderType | "error", metadataView?: React.ReactNode | null, messageView?: React.ReactNode | null, sx?: SxProps, copyContent?: string, }; const MessageContainer = (props: MessageContainerProps) => { const { type, sx, messageView, metadataView, copyContent } = props; const icon = getIcon(type); return {icon !== null && icon} {messageView} {copyContent && } {metadataView} ; }; const Message = (props: MessageProps) => { const { message, title, sx, className, chatSession, onExpand, expanded, expandable } = props; const [metaExpanded, setMetaExpanded] = useState(false); const theme = useTheme(); const type: ApiActivityType | ChatSenderType | "error" = ('activity' in message) ? message.activity : ('error' in message) ? 'error' : (message as ChatMessage).role; const style: any = getStyle(theme, type); const handleMetaExpandClick = () => { setMetaExpanded(!metaExpanded); }; let content; if (typeof (message.content) === "string") { content = message.content.trim(); } else { console.error(`message content is not a string`); return (<>) } if (!content) { return (<>) }; const messageView = ( ); let metadataView = (<>); const metadata: ChatMessageMetaData | null = ('metadata' in message) ? (message.metadata as ChatMessageMetaData || null) : null; if (metadata) { metadataView = ( ); } const copyContent = (type === 'assistant') ? message.content : undefined; if (!expandable) { /* When not expandable, the styles are applied directly to MessageContainer */ return (<> {messageView && } ); } // Determine if Accordion is controlled const isControlled = typeof expanded === 'boolean' && typeof onExpand === 'function'; return ( { isControlled && onExpand && onExpand(newExpanded) }} sx={{ ...sx, ...style }}> } slotProps={{ content: { sx: { display: 'flex', justifyItems: 'center', m: 0, p: 0, fontWeight: 'bold', fontSize: '1.1rem', }, }, }}> {title || ''} ); } export type { MessageProps, }; export { Message, MessageMeta, };