perf: Add performance optimizations and Tanuki avatar
Frontend: - Add memoization to MessageList with custom comparison function - Implement incremental caching for processedMessages to avoid O(n) rebuilds during streaming - Wrap Message component with memo() - Add better error handling for file uploads in SessionContext Backend: - Improve upload error handling with proper response checks Infrastructure: - Add client_max_body_size 100m to nginx for file uploads - Add Tanuki avatar (optimized 256x256, 77KB) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -119,8 +119,28 @@ function SystemHints({ reminders, inline = false }) {
|
||||
);
|
||||
}
|
||||
|
||||
// Not using memo here - needs to re-render when HostContext updates
|
||||
export function MessageList({ messages, isProcessing, onSendMessage, hostId }) {
|
||||
// Custom comparison for MessageList - only re-render when these specific things change
|
||||
const messageListPropsAreEqual = (prev, next) => {
|
||||
// Check if messages array length changed
|
||||
if (prev.messages.length !== next.messages.length) return false;
|
||||
|
||||
// Check if last message content changed (for streaming)
|
||||
if (prev.messages.length > 0 && next.messages.length > 0) {
|
||||
const prevLast = prev.messages[prev.messages.length - 1];
|
||||
const nextLast = next.messages[next.messages.length - 1];
|
||||
if (prevLast.content !== nextLast.content) return false;
|
||||
if (prevLast.type !== nextLast.type) return false;
|
||||
}
|
||||
|
||||
// Check other props
|
||||
return (
|
||||
prev.isProcessing === next.isProcessing &&
|
||||
prev.onSendMessage === next.onSendMessage &&
|
||||
prev.hostId === next.hostId
|
||||
);
|
||||
};
|
||||
|
||||
export const MessageList = memo(function MessageList({ messages, isProcessing, onSendMessage, hostId }) {
|
||||
const { hosts } = useHosts();
|
||||
const hostConfig = hosts[hostId] || null;
|
||||
const containerRef = useRef(null);
|
||||
@@ -174,8 +194,35 @@ export function MessageList({ messages, isProcessing, onSendMessage, hostId }) {
|
||||
setNewMessageCount(0);
|
||||
}, []);
|
||||
|
||||
// Cache for incremental updates - avoid full rebuild on streaming content changes
|
||||
const processedCacheRef = useRef({ messages: [], result: [], toolResultMap: new Map() });
|
||||
|
||||
// Preprocess messages to pair tool_use with tool_result
|
||||
// Optimized: only rebuild when structure changes, not content
|
||||
const processedMessages = useMemo(() => {
|
||||
const cache = processedCacheRef.current;
|
||||
|
||||
// Fast path: if only last message content changed (streaming), return cached result
|
||||
if (cache.messages.length === messages.length && messages.length > 0) {
|
||||
const lastCached = cache.messages[cache.messages.length - 1];
|
||||
const lastCurrent = messages[messages.length - 1];
|
||||
|
||||
// Check if structure is same (only content might have changed)
|
||||
if (lastCached.type === lastCurrent.type &&
|
||||
lastCached.timestamp === lastCurrent.timestamp &&
|
||||
lastCached.toolUseId === lastCurrent.toolUseId) {
|
||||
// Content update only - update the cached result's last item content
|
||||
if (cache.result.length > 0 && lastCurrent.type === 'assistant') {
|
||||
cache.result[cache.result.length - 1] = {
|
||||
...cache.result[cache.result.length - 1],
|
||||
content: lastCurrent.content
|
||||
};
|
||||
}
|
||||
return cache.result;
|
||||
}
|
||||
}
|
||||
|
||||
// Full rebuild needed
|
||||
const result = [];
|
||||
const toolResultMap = new Map();
|
||||
|
||||
@@ -207,6 +254,11 @@ export function MessageList({ messages, isProcessing, onSendMessage, hostId }) {
|
||||
}
|
||||
});
|
||||
|
||||
// Update cache
|
||||
cache.messages = messages;
|
||||
cache.result = result;
|
||||
cache.toolResultMap = toolResultMap;
|
||||
|
||||
return result;
|
||||
}, [messages]);
|
||||
|
||||
@@ -265,9 +317,10 @@ export function MessageList({ messages, isProcessing, onSendMessage, hostId }) {
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}, messageListPropsAreEqual);
|
||||
|
||||
function Message({ message, onSendMessage, hostConfig }) {
|
||||
// Memoize Message component to prevent re-renders during streaming
|
||||
const Message = memo(function Message({ message, onSendMessage, hostConfig }) {
|
||||
const { type, content, tool, input, timestamp, toolUseId, attachments } = message;
|
||||
const { user } = useAuth();
|
||||
|
||||
@@ -434,7 +487,7 @@ function Message({ message, onSendMessage, hostConfig }) {
|
||||
};
|
||||
|
||||
return <div>{renderContent()}</div>;
|
||||
}
|
||||
});
|
||||
|
||||
// Tool configuration with icons, colors, and display logic
|
||||
const TOOL_CONFIG = {
|
||||
|
||||
Reference in New Issue
Block a user