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,
};