import React, { useState } from 'react';
import { Sparkles, Clock, Terminal, ChevronDown, ChevronUp, AlertCircle, Brain, Cpu } from 'lucide-react';
// Render LaTeX and text mixed together
function RenderLatex({ text }) {
if (!text) return null;
// Split text by $$ (block math) first, then by $ (inline math)
const blockParts = text.split(/(\$\$.*?\$\$)/g);
return (
<>
{blockParts.map((bp, bpIdx) => {
if (bp.startsWith('$$') && bp.endsWith('$$')) {
const formula = bp.slice(2, -2);
try {
if (window.katex) {
const html = window.katex.renderToString(formula, { displayMode: true, throwOnError: false });
return
;
}
} catch (e) {
console.error(e);
}
return {bp}
;
}
// Inline math split
const inlineParts = bp.split(/(\$.*?\$)/g);
return (
{inlineParts.map((ip, ipIdx) => {
if (ip.startsWith('$') && ip.endsWith('$')) {
const formula = ip.slice(1, -1);
if (formula.trim()) {
try {
if (window.katex) {
const html = window.katex.renderToString(formula, { displayMode: false, throwOnError: false });
return ;
}
} catch (e) {
console.error(e);
}
}
return {ip};
}
return ;
})}
);
})}
>
);
}
// A simple local Markdown parser that converts basic markdown elements to safe HTML
function SafeMarkdown({ content }) {
if (!content) return null;
const parts = content.split(/(```[\s\S]*?```)/g);
return (
{parts.map((part, index) => {
if (part.startsWith('```') && part.endsWith('```')) {
const code = part.slice(3, -3).replace(/^\w+\n/, '');
return (
{code}
);
}
const formatted = part
.split('\n\n')
.map((para, paraIdx) => {
if (!para.trim()) return null;
// Handle bullet points
if (para.trim().startsWith('- ') || para.trim().startsWith('* ')) {
const items = para.split(/\n\s*[-*]\s+/);
return (
{items.map((item, itemIdx) => {
let cleanItem = item;
if (itemIdx === 0) {
cleanItem = item.replace(/^\s*[-*]\s+/, '');
}
if (!cleanItem.trim()) return null;
return (
-
);
})}
);
}
return (
);
});
return
{formatted};
})}
);
}
// Format bold (**), italics (*), and inline code (`)
function formatInline(text) {
return text
.replace(/\*\*([^*]+)\*\*/g, '$1')
.replace(/\*([^*]+)\*/g, '$1')
.replace(/`([^`]+)`/g, '$1')
.replace(/\n/g, '
');
}
export default function ChatWindow({
history = [],
loading,
error,
onSubmitFollowUp
}) {
const [openInspectors, setOpenInspectors] = useState({});
const toggleInspector = (idx) => {
setOpenInspectors(prev => ({
...prev,
[idx]: !prev[idx]
}));
};
// Render loading skeleton
if (loading && history.length === 0) {
return (
Socratic Tutor
Analyzing Sentiment...
--s
);
}
// Render error message
if (error) {
return (
);
}
// Render empty state
if (history.length === 0) {
return (
Welcome to Socratic Sentiment Tutor
Ask any question about math, science, or programming. The Socratic tutor will detect your mood and guide you towards the answers without giving them away directly.
);
}
return (
{/* Scrollable Conversation Thread */}
{history.map((msg, idx) => {
const isUser = msg.role === 'user';
if (isUser) {
return (
);
}
// Assistant / Tutor Bubble
return (
{/* Card Header with sentiment state & metrics */}
Socratic Tutor
{msg.sentiment && (
{msg.latency}s
{msg.tokens !== undefined && (
<>
|
{msg.tokens}t
>
)}
{msg.cost !== undefined && (
<>
|
${msg.cost.toFixed(5)}
>
)}
)}
{/* Chat Bubble Body */}
{/* Prompt Context Inspector Toggle */}
{msg.prompt_context && (
toggleInspector(idx)}
style={{ padding: '0.6rem 1.25rem', fontSize: '0.8rem', background: 'rgba(255,255,255,0.01)' }}
>
View Socratic Context Inspector
{openInspectors[idx] ? : }
{openInspectors[idx] && (
Detected Student Sentiment:
{msg.sentiment.replace(/_/g, ' ')}
Prompt Context:
{msg.prompt_context}
)}
)}
);
})}
{/* Loading Bubble when generating */}
{loading && (
Socratic Tutor thinking...
)}
{/* Follow-up / Reply Area */}
{history.length > 0 && !loading && (
Socratic Dialogue
Reply to the Socratic tutor's guide question to continue exploring the concept.
)}
);
}