import React, { useRef, useEffect } from 'react';
import CodeBlock from "./CodeBlock";
import { Box, Paper, CircularProgress, Typography } from "@mui/material";
import "./ChatBox.css"; // Add dot animation CSS
export default function ChatBox({ messages, loading }) {
const bottomRef = useRef(null);
useEffect(() => {
if (!bottomRef.current) return;
if (loading) {
bottomRef.current.scrollIntoView({ behavior: 'auto' });
} else {
// Smooth scroll after streaming ends with slight delay
setTimeout(() => {
bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
}, 80); // tweak timing based on UX
}
}, [messages, loading]);
return (
{messages.map((msg, i) =>
msg.content?.trim() ? (
) : null
)}
{loading && (
)}
);
}
const formatText = (text) => {
// ✅ Handle base64 images
const imageRegex = /\[IMAGE_START\](.*?)\[IMAGE_END\]/gs;
text = text.replace(imageRegex, (match, base64) => {
const src = `data:image/png;base64,${base64.trim()}`;
return `
`;
});
// ✅ Normalize line endings and remove excessive blank lines
text = text.replace(/\r\n|\r/g, '\n');
text = text.replace(/\n{3,}/g, '\n\n');
// ✅ Parse fenced code blocks (```code```)
text = text.replace(/```(\w+)?\n([\s\S]*?)```/g, (match, lang, code) => {
const language = lang ? ` class="language-${lang}"` : '';
return `
${code.trim().replace(//g, '>')}
`;
});
// ✅ Parse blockquotes
text = text.replace(/^> (.*)$/gm, '$1
');
// ✅ Headings
text = text.replace(/^### (.*)$/gm, '$1
');
// ✅ Horizontal rules
text = text.replace(/^---$/gm, '
');
// ✅ Bold (**text**) and italic (*text*)
text = text.replace(/\*\*(.*?)\*\*/g, '$1');
text = text.replace(/\*(.*?)\*/g, '$1');
// ✅ Emoji rendering using colon syntax (:smile:)
const emojiMap = {
smile: "😄",
sad: "😢",
heart: "❤️",
thumbs_up: "👍",
fire: "🔥",
check: "✅",
x: "❌",
star: "⭐",
rocket: "🚀",
warning: "⚠️",
};
text = text.replace(/:([a-z0-9_+-]+):/g, (match, name) => emojiMap[name] || match);
// ✅ Unordered list (bullets)
const listify = (lines, tag) =>
`<${tag}>` +
lines.map(item => `${item.replace(/^(\-|\d+\.)\s*/, '').trim()}`).join('') +
`${tag}>`;
text = text.replace(
/((?:^[-*] .+(?:\n|$))+)/gm,
(match) => listify(match.trim().split('\n'), 'ul')
);
// ✅ Ordered list (fix separate `1.` items issue)
text = text.replace(/^(\d+\. .+)$/gm, '__ORDERED__START__$1__ORDERED__END__');
text = text.replace(
/__ORDERED__START__(\d+\. .+?)__ORDERED__END__/gs,
(_, line) => `- ${line.replace(/^\d+\.\s*/, '')}
`
);
text = text.replace(/<\/ol>\s*/g, '');
// ✅ Markdown-style tables
text = text.replace(
/^\|(.+?)\|\n\|([-:| ]+)\|\n((?:\|.*\|\n?)*)/gm,
(_, headerRow, dividerRow, bodyRows) => {
const headers = headerRow.split('|').map(h => `| ${h.trim()} | `).join('');
const rows = bodyRows.trim().split('\n').map(r =>
'' + r.split('|').map(cell => `| ${cell.trim()} | `).join('') + '
'
).join('');
return ``;
}
);
// ✅ Paragraphs and line breaks inside paragraphs
const blocks = text.split(/\n{2,}/).map(block => {
if (
block.startsWith('') ||
block.startsWith('
') ||
block.startsWith('') ||
block.startsWith('') ||
block.startsWith('') ||
block.startsWith('') ||
block.startsWith('') ||
block.startsWith('
${block.trim().replace(/\n/g, '
')}`;
}
});
return blocks.join('\n');
};
function Message({ content }) {
const parts = content.split(/```(?:[a-z]*)\n([\s\S]*?)```/g);
return (
<>
{parts.map((part, i) =>
i % 2 === 1 ? (
) : (
)
)}
>
);
}