import { useRef, useEffect, useMemo, useState, useCallback } from 'react';
import { Box, Stack, Typography, IconButton, Button, Tooltip } from '@mui/material';
import CloseIcon from '@mui/icons-material/Close';
import RadioButtonUncheckedIcon from '@mui/icons-material/RadioButtonUnchecked';
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
import PlayCircleOutlineIcon from '@mui/icons-material/PlayCircleOutline';
import CodeIcon from '@mui/icons-material/Code';
import TerminalIcon from '@mui/icons-material/Terminal';
import ArticleIcon from '@mui/icons-material/Article';
import EditIcon from '@mui/icons-material/Edit';
import UndoIcon from '@mui/icons-material/Undo';
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
import CheckIcon from '@mui/icons-material/Check';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { vscDarkPlus, vs } from 'react-syntax-highlighter/dist/esm/styles/prism';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import { useAgentStore } from '@/store/agentStore';
import { useLayoutStore } from '@/store/layoutStore';
import { processLogs } from '@/utils/logProcessor';
// ── Helpers ──────────────────────────────────────────────────────
function tabIcon(id: string, language?: string) {
if (id === 'script' || language === 'python') return ;
if (id === 'tool_output' || language === 'markdown' || language === 'json')
return ;
return ;
}
function PlanStatusIcon({ status }: { status: string }) {
if (status === 'completed') return ;
if (status === 'in_progress') return ;
return ;
}
// ── Markdown styles (adapts via CSS vars) ────────────────────────
const markdownSx = {
color: 'var(--text)',
fontSize: '13px',
lineHeight: 1.6,
'& p': { m: 0, mb: 1.5, '&:last-child': { mb: 0 } },
'& pre': {
bgcolor: 'var(--code-bg)',
p: 1.5,
borderRadius: 1,
overflow: 'auto',
fontSize: '12px',
border: '1px solid var(--tool-border)',
},
'& code': {
bgcolor: 'var(--hover-bg)',
px: 0.5,
py: 0.25,
borderRadius: 0.5,
fontSize: '12px',
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, monospace',
},
'& pre code': { bgcolor: 'transparent', p: 0 },
'& a': {
color: 'var(--accent-yellow)',
textDecoration: 'none',
'&:hover': { textDecoration: 'underline' },
},
'& ul, & ol': { pl: 2.5, my: 1 },
'& li': { mb: 0.5 },
'& table': {
borderCollapse: 'collapse',
width: '100%',
my: 2,
fontSize: '12px',
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, monospace',
},
'& th': {
borderBottom: '2px solid var(--border-hover)',
textAlign: 'left',
p: 1,
fontWeight: 600,
},
'& td': {
borderBottom: '1px solid var(--tool-border)',
p: 1,
},
'& h1, & h2, & h3, & h4': { mt: 2, mb: 1, fontWeight: 600 },
'& h1': { fontSize: '1.25rem' },
'& h2': { fontSize: '1.1rem' },
'& h3': { fontSize: '1rem' },
'& blockquote': {
borderLeft: '3px solid var(--accent-yellow)',
pl: 2,
ml: 0,
color: 'var(--muted-text)',
},
} as const;
// ── Component ────────────────────────────────────────────────────
export default function CodePanel() {
const { panelContent, panelTabs, activePanelTab, setActivePanelTab, removePanelTab, plan, updatePanelTabContent, setEditedScript } =
useAgentStore();
const { setRightPanelOpen, themeMode } = useLayoutStore();
const scrollRef = useRef(null);
const textareaRef = useRef(null);
const [isEditing, setIsEditing] = useState(false);
const [editedContent, setEditedContent] = useState('');
const [originalContent, setOriginalContent] = useState('');
const [copied, setCopied] = useState(false);
const activeTab = panelTabs.find((t) => t.id === activePanelTab);
const currentContent = activeTab || panelContent;
const hasTabs = panelTabs.length > 0;
const isDark = themeMode === 'dark';
const syntaxTheme = isDark ? vscDarkPlus : vs;
// Check if this is an editable script tab
const isEditableScript = activeTab?.id === 'script' && activeTab?.language === 'python';
const hasUnsavedChanges = isEditing && editedContent !== originalContent;
// Sync edited content when switching tabs or content changes
useEffect(() => {
if (currentContent?.content && isEditableScript) {
setOriginalContent(currentContent.content);
if (!isEditing) {
setEditedContent(currentContent.content);
}
}
}, [currentContent?.content, isEditableScript, isEditing]);
// Exit editing when switching away from script tab
useEffect(() => {
if (!isEditableScript && isEditing) {
setIsEditing(false);
}
}, [isEditableScript, isEditing]);
const handleStartEdit = useCallback(() => {
if (currentContent?.content) {
setEditedContent(currentContent.content);
setOriginalContent(currentContent.content);
setIsEditing(true);
setTimeout(() => textareaRef.current?.focus(), 0);
}
}, [currentContent?.content]);
const handleCancelEdit = useCallback(() => {
setEditedContent(originalContent);
setIsEditing(false);
}, [originalContent]);
const handleSaveEdit = useCallback(() => {
if (activeTab && editedContent !== originalContent) {
updatePanelTabContent(activeTab.id, editedContent);
const toolCallId = activeTab.parameters?.tool_call_id as string | undefined;
if (toolCallId) {
setEditedScript(toolCallId, editedContent);
}
setOriginalContent(editedContent);
}
setIsEditing(false);
}, [activeTab, editedContent, originalContent, updatePanelTabContent, setEditedScript]);
const handleCopy = useCallback(async () => {
const contentToCopy = isEditing ? editedContent : (currentContent?.content || '');
if (contentToCopy) {
try {
await navigator.clipboard.writeText(contentToCopy);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch (err) {
console.error('Failed to copy:', err);
}
}
}, [isEditing, editedContent, currentContent?.content]);
const displayContent = useMemo(() => {
if (!currentContent?.content) return '';
if (!currentContent.language || currentContent.language === 'text') {
return processLogs(currentContent.content);
}
return currentContent.content;
}, [currentContent?.content, currentContent?.language]);
useEffect(() => {
if (scrollRef.current && activePanelTab === 'logs') {
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
}
}, [displayContent, activePanelTab]);
// ── Syntax-highlighted code block (DRY) ────────────────────────
const renderSyntaxBlock = (language: string) => (
{displayContent}
);
// ── Content renderer ───────────────────────────────────────────
const renderContent = () => {
if (!currentContent?.content) {
return (
NO CONTENT TO DISPLAY
);
}
// Editing mode: show textarea
if (isEditing && isEditableScript) {
return (