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 (