import { useEffect, useMemo, useRef, useState } from 'react'; import { CodeBlock } from '@/components/ui/CodeBlock'; import { IconCheckCircle, IconCodeWrap, IconCrossCircle, IconLandingAI, IconListUnordered, IconTerminalWindow, IconUser, IconGlowingDot, } from '@/components/ui/Icons'; import { WIPChunkBodyGroup, formatStreamLogs } from '@/lib/utils/content'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '../ui/Table'; import { Button } from '../ui/Button'; import { Dialog, DialogContent, DialogTrigger } from '../ui/Dialog'; import Img from '../ui/Img'; import CodeResultDisplay from '../CodeResultDisplay'; import { useAtom } from 'jotai'; import { selectedMessageId } from '@/state/chat'; import { Message } from '@prisma/client'; import { Separator } from '../ui/Separator'; import { cn } from '@/lib/utils'; import toast from 'react-hot-toast'; import { EXECUTE_CODE_FAILURE_TITLE, EXECUTE_CODE_SUCCESS_TITLE, EXECUTE_CODE_TITLE, GENERATE_CODE_TITLE, PLAN_TITLE, TOOLS_TITLE, } from '@/lib/constants'; export interface ChatMessageProps { message: Message; loading?: boolean; wipAssistantMessage?: PrismaJson.MessageBody[]; } const getParsedStreamLogs = (content: string) => { const streamLogs = content.split('\n').filter(log => !!log); const buffer = streamLogs.pop(); const parsedStreamLogs: WIPChunkBodyGroup[] = []; try { streamLogs.forEach(streamLog => parsedStreamLogs.push(JSON.parse(streamLog)), ); } catch { toast.error('Error parsing stream logs'); } if (buffer) { try { const lastLog = JSON.parse(buffer); parsedStreamLogs.push(lastLog); } catch { console.log(buffer); } } return parsedStreamLogs; }; export const ChatMessage: React.FC = ({ message, wipAssistantMessage, loading, }) => { const [messageId, setMessageId] = useAtom(selectedMessageId); const { id, mediaUrl, prompt, response, result, responseBody } = message; const { formattedSections, finalResult, finalError } = useMemo( () => formatStreamLogs( wipAssistantMessage ?? responseBody ?? (response ? getParsedStreamLogs(response) : []), result, ), [wipAssistantMessage, responseBody, response, result], ); return (
{ if (result) { setMessageId(id); } }} >

{prompt}

{mediaUrl && ( <> {mediaUrl?.endsWith('.mp4') ? (
{!!formattedSections.length && ( <>
{formattedSections.map((section, index) => ( {ChunkStatusToIconDict[section.status]} ))}
{finalResult && ( <>

✨ Coding complete

)} {!finalResult && finalError && ( <>

❌ {finalError.name}

)}
)}
); }; const ChunkStatusToIconDict: Record< WIPChunkBodyGroup['status'], React.ReactElement > = { started: , completed: , running: , failed: , }; const ChunkTypeToText: React.FC<{ chunk: WIPChunkBodyGroup; useTimer: boolean; }> = ({ chunk, useTimer }) => { const { status, type, timestamp, duration } = chunk; const [mSeconds, setMSeconds] = useState(0); const isExecuting = !['completed', 'failed'].includes(status); useEffect(() => { if (isExecuting && timestamp && useTimer) { const timerId = setInterval(() => { setMSeconds(Date.now() - Date.parse(timestamp)); }, 200); return () => clearInterval(timerId); } }, [isExecuting, timestamp, useTimer]); const displayMs = isExecuting && useTimer ? mSeconds : duration; const durationDisplay = displayMs ? `(${Math.round(displayMs / 100) / 10}s)` : ''; if (type === 'plans') return (

{PLAN_TITLE} {durationDisplay}

); if (type === 'tools') return (

{TOOLS_TITLE} {durationDisplay}

); if (type === 'code' && status === 'started') return (

{GENERATE_CODE_TITLE} {durationDisplay}

); if (type === 'code' && status === 'running') return (

{EXECUTE_CODE_TITLE} {durationDisplay}

); if (type === 'code' && status === 'completed') return (

{EXECUTE_CODE_SUCCESS_TITLE} {durationDisplay}

); if (type === 'code' && status === 'failed') return (

{EXECUTE_CODE_FAILURE_TITLE} {durationDisplay}

); return null; }; const ChunkPayloadAction: React.FC<{ payload: WIPChunkBodyGroup['payload']; }> = ({ payload }) => { if (!payload) return null; 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 ( e.preventDefault()} > {keyArray.map(header => ( {header} ))} {payload.map((line, index) => ( {keyArray.map(header => header === 'documentation' ? ( ) : ( {line[header]} ), )} ))}
); } else if ((payload as PrismaJson.FinalCodeBody['payload']).code) { return ( ); } return null; }; export default ChatMessage;