// Inspired by Chatbot-UI and modified to fit the needs of this project // @see https://github.com/mckaywrigley/chatbot-ui/blob/main/components/Chat/ChatMessage.tsx import remarkGfm from 'remark-gfm'; import remarkMath from 'remark-math'; import rehypeRaw from 'rehype-raw'; import { useMemo, useState } from 'react'; import { cn } from '@/lib/utils'; import { CodeBlock } from '@/components/ui/CodeBlock'; import { MemoizedReactMarkdown } from '@/components/chat/MemoizedReactMarkdown'; import { IconCheckCircle, IconChevronDoubleRight, IconCodeWrap, IconCrossCircle, IconLandingAI, IconListUnordered, IconTerminalWindow, IconUser, IconOutput, IconLog, } from '@/components/ui/Icons'; import { MessageBase } from '../../lib/types'; import Img from '../ui/Img'; import { ChunkBody, CodeResult, formatStreamLogs } from '@/lib/messageUtils'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '../ui/Table'; import { Button } from '../ui/Button'; import { Separator } from '../ui/Separator'; import { Tooltip, TooltipContent, TooltipTrigger, } from '@/components/ui/Tooltip'; import { Dialog, DialogContent, DialogTrigger } from '../ui/Dialog'; export interface ChatMessageProps { message: MessageBase; isLoading: boolean; } const Markdown: React.FC<{ content: string; setDetails?: (val: string) => void; }> = ({ content, setDetails }) => { return ( <> {children}; }, thead({ children, ...props }) { return {children}; }, th({ children, ...props }) { return {children}; }, tr({ children, ...props }) { return {children}; }, td({ children, ...props }) { return {children}; }, button({ children, ...props }) { if ('data-details' in props && setDetails) { return ( ); } return ; }, p({ children, ...props }) { if ( props.node.children.some( child => child.type === 'element' && child.tagName === 'img', ) ) { return (

{children}

); } return

{children}

; }, img(props) { if (props.src?.endsWith('.mp4')) { return (
); }; export function ChatMessage({ message, isLoading }: ChatMessageProps) { const { role, content } = message; return role === 'user' ? ( ) : ( ); } const UserChatMessage: React.FC<{ content: string; }> = ({ content }) => { return (
{content && }
); }; const ChunkStatusToIconDict: Record = { started: , completed: , running: , failed: , }; const ChunkTypeToTextDict: Record = { plans: 'Creating instructions', tools: 'Retrieving tools', code: 'Generating code', final_code: 'Final result', }; const ChunkPayloadAction: React.FC<{ payload: ChunkBody['payload']; }> = ({ payload }) => { if (Array.isArray(payload)) { // [{title: 123, content, 345}, {title: ..., content: ...}] => ['title', 'content'] const keyArray = Array.from( payload.reduce((acc, curr) => { Object.keys(curr).forEach(key => acc.add(key)); return acc; }, new Set()), ); return ( {keyArray.map(header => ( {header} ))} {payload.map((line, index) => ( {keyArray.map(header => header === 'documentation' ? ( ) : ( {line[header]} ), )} ))}
); } else { return ( ); } }; const CodeResultDisplay: React.FC<{ codeResult: CodeResult; }> = ({ codeResult }) => { const { code, test, result } = codeResult; const getDetail = () => { if (!result) return {}; try { const detail = JSON.parse(result); return { results: detail.results, stderr: detail.logs.stderr, stdout: detail.logs.stdout, }; } catch { return {}; } }; const { results, stderr, stdout } = getDetail(); return (
{Array.isArray(stdout) && ( )} {Array.isArray(stderr) && ( )}
); }; const AssistantChatMessage: React.FC<{ content: string; }> = ({ content }) => { const [formattedSections, codeResult] = useMemo( () => formatStreamLogs(content), [content], ); return (
{formattedSections.map(section => ( {ChunkStatusToIconDict[section.status]} {ChunkTypeToTextDict[section.type]} ))}
{codeResult && }
); }; export default UserChatMessage;