perf: Critical performance & stability fixes
Frontend: - Lazy load SyntaxHighlighter via React.lazy() - saves ~500KB from initial bundle - Add LazyCodeBlock wrapper with Suspense fallback - Main bundle now 448KB instead of 1.1MB Backend: - Implement WebSocket message queue with backpressure handling - Add heartbeat timeout check (60s) to detect zombie connections - Add process startup timeout (30s) and max lifetime (24h) - Fix restart race condition with timeout fallback - Replace sessions Map with LRU Map (max 100 sessions) - Add periodic cleanup for idle sessions (4h) - Track session activity timestamps These changes address critical issues identified in performance analysis: - No more unbounded memory growth from sessions - No more stuck isRestarting state - No more message drops during heavy Claude output - No more zombie WebSocket connections - Faster initial page load 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useRef, useState, useMemo, memo, useCallback } from 'react';
|
||||
import { useEffect, useRef, useState, useMemo, memo, useCallback, lazy, Suspense } from 'react';
|
||||
import TextareaAutosize from 'react-textarea-autosize';
|
||||
import {
|
||||
User, Bot, Terminal, CheckCircle, AlertCircle, Info,
|
||||
@@ -10,9 +10,42 @@ import ReactMarkdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
import { useHosts } from '../contexts/HostContext';
|
||||
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
||||
|
||||
// Lazy load SyntaxHighlighter - saves ~500KB from initial bundle
|
||||
const SyntaxHighlighter = lazy(() =>
|
||||
import('react-syntax-highlighter').then(mod => ({
|
||||
default: mod.Prism
|
||||
}))
|
||||
);
|
||||
|
||||
// Import style separately (small JSON, OK to load eagerly for consistency)
|
||||
import { oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
||||
|
||||
// Fallback component for code blocks while SyntaxHighlighter loads
|
||||
const CodeFallback = memo(function CodeFallback({ children }) {
|
||||
return (
|
||||
<pre className="bg-dark-900 rounded-lg p-3 text-xs text-dark-300 font-mono overflow-x-auto animate-pulse">
|
||||
<code>{children}</code>
|
||||
</pre>
|
||||
);
|
||||
});
|
||||
|
||||
// Wrapper for lazy-loaded SyntaxHighlighter with Suspense
|
||||
const LazyCodeBlock = memo(function LazyCodeBlock({ language, style, customStyle, children, ...props }) {
|
||||
return (
|
||||
<Suspense fallback={<CodeFallback>{children}</CodeFallback>}>
|
||||
<SyntaxHighlighter
|
||||
language={language}
|
||||
style={style}
|
||||
customStyle={customStyle}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</SyntaxHighlighter>
|
||||
</Suspense>
|
||||
);
|
||||
});
|
||||
|
||||
// Helper to extract and filter system-reminder tags
|
||||
function parseSystemReminders(text) {
|
||||
if (!text || typeof text !== 'string') return { content: text || '', reminders: [] };
|
||||
@@ -415,7 +448,7 @@ const Message = memo(function Message({ message, onSendMessage, hostConfig }) {
|
||||
code({ node, inline, className, children, ...props }) {
|
||||
const match = /language-(\w+)/.exec(className || '');
|
||||
return !inline && match ? (
|
||||
<SyntaxHighlighter
|
||||
<LazyCodeBlock
|
||||
style={oneDark}
|
||||
language={match[1]}
|
||||
PreTag="div"
|
||||
@@ -428,7 +461,7 @@ const Message = memo(function Message({ message, onSendMessage, hostConfig }) {
|
||||
{...props}
|
||||
>
|
||||
{String(children).replace(/\n$/, '')}
|
||||
</SyntaxHighlighter>
|
||||
</LazyCodeBlock>
|
||||
) : (
|
||||
<code className={className} {...props}>
|
||||
{children}
|
||||
@@ -854,13 +887,13 @@ const ToolUseCard = memo(function ToolUseCard({ tool, input, result, onSendMessa
|
||||
if (tool === 'Bash' && input.command) {
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<SyntaxHighlighter
|
||||
<LazyCodeBlock
|
||||
language="bash"
|
||||
style={oneDark}
|
||||
customStyle={{ margin: 0, padding: '8px', fontSize: '11px', borderRadius: '4px' }}
|
||||
>
|
||||
{input.command}
|
||||
</SyntaxHighlighter>
|
||||
</LazyCodeBlock>
|
||||
{input.description && (
|
||||
<div className="text-xs text-dark-500">Description: {input.description}</div>
|
||||
)}
|
||||
@@ -1055,7 +1088,7 @@ const ToolUseCard = memo(function ToolUseCard({ tool, input, result, onSendMessa
|
||||
<span className="text-dark-500">{lines} lines</span>
|
||||
</div>
|
||||
{input.content && (
|
||||
<SyntaxHighlighter
|
||||
<LazyCodeBlock
|
||||
language={language}
|
||||
style={oneDark}
|
||||
customStyle={{ margin: 0, padding: '8px', fontSize: '11px', borderRadius: '4px', maxHeight: '200px' }}
|
||||
@@ -1063,7 +1096,7 @@ const ToolUseCard = memo(function ToolUseCard({ tool, input, result, onSendMessa
|
||||
wrapLongLines={true}
|
||||
>
|
||||
{input.content}
|
||||
</SyntaxHighlighter>
|
||||
</LazyCodeBlock>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
@@ -1221,13 +1254,13 @@ const ToolUseCard = memo(function ToolUseCard({ tool, input, result, onSendMessa
|
||||
|
||||
// Default JSON view
|
||||
return (
|
||||
<SyntaxHighlighter
|
||||
<LazyCodeBlock
|
||||
language="json"
|
||||
style={oneDark}
|
||||
customStyle={{ margin: 0, padding: '8px', fontSize: '11px', borderRadius: '4px' }}
|
||||
>
|
||||
{formatInput()}
|
||||
</SyntaxHighlighter>
|
||||
</LazyCodeBlock>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1323,14 +1356,14 @@ const ToolUseCard = memo(function ToolUseCard({ tool, input, result, onSendMessa
|
||||
{/* Result Content */}
|
||||
<div className="max-h-48 overflow-y-auto">
|
||||
{resultLooksLikeCode ? (
|
||||
<SyntaxHighlighter
|
||||
<LazyCodeBlock
|
||||
language="javascript"
|
||||
style={oneDark}
|
||||
customStyle={{ margin: 0, padding: '8px', fontSize: '11px', background: 'transparent' }}
|
||||
wrapLongLines={true}
|
||||
>
|
||||
{resultData.contentStr}
|
||||
</SyntaxHighlighter>
|
||||
</LazyCodeBlock>
|
||||
) : (
|
||||
<pre className="text-xs text-dark-400 whitespace-pre-wrap font-mono">{resultData.contentStr}</pre>
|
||||
)}
|
||||
@@ -1433,14 +1466,14 @@ const ToolResultCard = memo(function ToolResultCard({ content, isSuccess = true
|
||||
{contentStr && (
|
||||
<div className="border-t border-dark-700/50 bg-dark-900/50 max-h-48 overflow-y-auto">
|
||||
{looksLikeCode ? (
|
||||
<SyntaxHighlighter
|
||||
<LazyCodeBlock
|
||||
language="javascript"
|
||||
style={oneDark}
|
||||
customStyle={{ margin: 0, padding: '12px', fontSize: '11px', background: 'transparent' }}
|
||||
wrapLongLines={true}
|
||||
>
|
||||
{contentStr}
|
||||
</SyntaxHighlighter>
|
||||
</LazyCodeBlock>
|
||||
) : (
|
||||
<pre className="text-xs text-dark-400 whitespace-pre-wrap p-3 font-mono">{contentStr}</pre>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user