From 5d69b449be19d6cd124fbd93fb6014bf6097557d Mon Sep 17 00:00:00 2001 From: Mufeed VH Date: Wed, 25 Jun 2025 00:52:07 +0530 Subject: [PATCH] refactor(ui): improve message visibility filtering and optimize bundle - Refactor message filtering logic across AgentExecution, ClaudeCodeSession, SessionOutputViewer, and StreamMessage components to better handle visibility of user messages with tool results - Replace inefficient every() loop with for-loop for better performance when checking message content visibility - Add renderedSomething tracking in StreamMessage to prevent rendering empty components - Optimize SessionOutputViewer with useMemo for displayableMessages filtering - Remove unused GitHub API response fields (url, html_url, git_url) in Rust agents command - Add Vite build configuration with manual code splitting for better bundle optimization - Remove TypeScript expect-error comment for process global variable These changes improve UI performance by reducing unnecessary re-renders and bundle size through better code splitting. --- src-tauri/src/commands/agents.rs | 3 -- src/components/AgentExecution.tsx | 63 +++++++++++++----------- src/components/ClaudeCodeSession.tsx | 67 ++++++++++++++------------ src/components/SessionOutputViewer.tsx | 47 ++++++++++++++++-- src/components/StreamMessage.tsx | 61 ++++++++++++++--------- vite.config.ts | 23 ++++++++- 6 files changed, 176 insertions(+), 88 deletions(-) diff --git a/src-tauri/src/commands/agents.rs b/src-tauri/src/commands/agents.rs index b3b76a3..38240f0 100644 --- a/src-tauri/src/commands/agents.rs +++ b/src-tauri/src/commands/agents.rs @@ -1952,9 +1952,6 @@ struct GitHubApiResponse { path: String, sha: String, size: i64, - url: String, - html_url: String, - git_url: String, download_url: Option, #[serde(rename = "type")] file_type: String, diff --git a/src/components/AgentExecution.tsx b/src/components/AgentExecution.tsx index a5f1b64..7af66f7 100644 --- a/src/components/AgentExecution.tsx +++ b/src/components/AgentExecution.tsx @@ -103,45 +103,54 @@ export const AgentExecution: React.FC = ({ // Skip empty user messages if (message.type === "user" && message.message) { + if (message.isMeta) return false; + const msg = message.message; if (!msg.content || (Array.isArray(msg.content) && msg.content.length === 0)) { return false; } - // Check if this is a user message with only tool results that are already displayed + // Check if user message has visible content by checking its parts if (Array.isArray(msg.content)) { - const hasOnlyHiddenToolResults = msg.content.every((content: any) => { - if (content.type !== "tool_result") return false; - - // Check if this tool result should be hidden - let hasCorrespondingWidget = false; - if (content.tool_use_id) { - // Look for the matching tool_use in previous assistant messages - for (let i = index - 1; i >= 0; i--) { - const prevMsg = messages[i]; - if (prevMsg.type === 'assistant' && prevMsg.message?.content && Array.isArray(prevMsg.message.content)) { - const toolUse = prevMsg.message.content.find((c: any) => - c.type === 'tool_use' && c.id === content.tool_use_id - ); - if (toolUse) { - const toolName = toolUse.name?.toLowerCase(); - const toolsWithWidgets = [ - 'task', 'edit', 'multiedit', 'todowrite', 'ls', 'read', - 'glob', 'bash', 'write', 'grep' - ]; - if (toolsWithWidgets.includes(toolName) || toolUse.name?.startsWith('mcp__')) { - hasCorrespondingWidget = true; + let hasVisibleContent = false; + for (const content of msg.content) { + if (content.type === "text") { + hasVisibleContent = true; + break; + } else if (content.type === "tool_result") { + // Check if this tool result will be skipped by a widget + let willBeSkipped = false; + if (content.tool_use_id) { + // Look for the matching tool_use in previous assistant messages + for (let i = index - 1; i >= 0; i--) { + const prevMsg = messages[i]; + if (prevMsg.type === 'assistant' && prevMsg.message?.content && Array.isArray(prevMsg.message.content)) { + const toolUse = prevMsg.message.content.find((c: any) => + c.type === 'tool_use' && c.id === content.tool_use_id + ); + if (toolUse) { + const toolName = toolUse.name?.toLowerCase(); + const toolsWithWidgets = [ + 'task', 'edit', 'multiedit', 'todowrite', 'ls', 'read', + 'glob', 'bash', 'write', 'grep' + ]; + if (toolsWithWidgets.includes(toolName) || toolUse.name?.startsWith('mcp__')) { + willBeSkipped = true; + } + break; } - break; } } } + + if (!willBeSkipped) { + hasVisibleContent = true; + break; + } } - - return hasCorrespondingWidget && !content.is_error; - }); + } - if (hasOnlyHiddenToolResults) { + if (!hasVisibleContent) { return false; } } diff --git a/src/components/ClaudeCodeSession.tsx b/src/components/ClaudeCodeSession.tsx index df814e5..745ba51 100644 --- a/src/components/ClaudeCodeSession.tsx +++ b/src/components/ClaudeCodeSession.tsx @@ -117,52 +117,57 @@ export const ClaudeCodeSession: React.FC = ({ return false; } - // Skip empty user messages + // Skip user messages that only contain tool results that are already displayed if (message.type === "user" && message.message) { + if (message.isMeta) return false; + const msg = message.message; if (!msg.content || (Array.isArray(msg.content) && msg.content.length === 0)) { return false; } - - // Check if this is a user message with only tool results that are already displayed + if (Array.isArray(msg.content)) { - const hasOnlyHiddenToolResults = msg.content.every((content: any) => { - if (content.type !== "tool_result") return false; - - // Check if this tool result should be hidden - let hasCorrespondingWidget = false; - if (content.tool_use_id) { - // Look for the matching tool_use in previous assistant messages - for (let i = index - 1; i >= 0; i--) { - const prevMsg = messages[i]; - if (prevMsg.type === 'assistant' && prevMsg.message?.content && Array.isArray(prevMsg.message.content)) { - const toolUse = prevMsg.message.content.find((c: any) => - c.type === 'tool_use' && c.id === content.tool_use_id - ); - if (toolUse) { - const toolName = toolUse.name?.toLowerCase(); - const toolsWithWidgets = [ - 'task', 'edit', 'multiedit', 'todowrite', 'ls', 'read', - 'glob', 'bash', 'write', 'grep' - ]; - if (toolsWithWidgets.includes(toolName) || toolUse.name?.startsWith('mcp__')) { - hasCorrespondingWidget = true; + let hasVisibleContent = false; + for (const content of msg.content) { + if (content.type === "text") { + hasVisibleContent = true; + break; + } + if (content.type === "tool_result") { + let willBeSkipped = false; + if (content.tool_use_id) { + // Look for the matching tool_use in previous assistant messages + for (let i = index - 1; i >= 0; i--) { + const prevMsg = messages[i]; + if (prevMsg.type === 'assistant' && prevMsg.message?.content && Array.isArray(prevMsg.message.content)) { + const toolUse = prevMsg.message.content.find((c: any) => + c.type === 'tool_use' && c.id === content.tool_use_id + ); + if (toolUse) { + const toolName = toolUse.name?.toLowerCase(); + const toolsWithWidgets = [ + 'task', 'edit', 'multiedit', 'todowrite', 'ls', 'read', + 'glob', 'bash', 'write', 'grep' + ]; + if (toolsWithWidgets.includes(toolName) || toolUse.name?.startsWith('mcp__')) { + willBeSkipped = true; + } + break; } - break; } } } + if (!willBeSkipped) { + hasVisibleContent = true; + break; + } } - - return hasCorrespondingWidget && !content.is_error; - }); - - if (hasOnlyHiddenToolResults) { + } + if (!hasVisibleContent) { return false; } } } - return true; }); }, [messages]); diff --git a/src/components/SessionOutputViewer.tsx b/src/components/SessionOutputViewer.tsx index b42fc08..0ac1ebf 100644 --- a/src/components/SessionOutputViewer.tsx +++ b/src/components/SessionOutputViewer.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useRef } from 'react'; +import { useState, useEffect, useRef, useMemo } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { X, Maximize2, Minimize2, Copy, RefreshCw, RotateCcw, ChevronDown } from 'lucide-react'; import { Button } from '@/components/ui/button'; @@ -282,6 +282,47 @@ export function SessionOutputViewer({ session, onClose, className }: SessionOutp loadOutput(); }, [session.id]); + const displayableMessages = useMemo(() => { + return messages.filter((message, index) => { + if (message.isMeta && !message.leafUuid && !message.summary) return false; + + if (message.type === "user" && message.message) { + if (message.isMeta) return false; + + const msg = message.message; + if (!msg.content || (Array.isArray(msg.content) && msg.content.length === 0)) return false; + + if (Array.isArray(msg.content)) { + let hasVisibleContent = false; + for (const content of msg.content) { + if (content.type === "text") { hasVisibleContent = true; break; } + if (content.type === "tool_result") { + let willBeSkipped = false; + if (content.tool_use_id) { + for (let i = index - 1; i >= 0; i--) { + const prevMsg = messages[i]; + if (prevMsg.type === 'assistant' && prevMsg.message?.content && Array.isArray(prevMsg.message.content)) { + const toolUse = prevMsg.message.content.find((c: any) => c.type === 'tool_use' && c.id === content.tool_use_id); + if (toolUse) { + const toolName = toolUse.name?.toLowerCase(); + const toolsWithWidgets = ['task','edit','multiedit','todowrite','ls','read','glob','bash','write','grep']; + if (toolsWithWidgets.includes(toolName) || toolUse.name?.startsWith('mcp__')) { + willBeSkipped = true; + } + break; + } + } + } + } + if (!willBeSkipped) { hasVisibleContent = true; break; } + } + } + if (!hasVisibleContent) return false; + } + } + return true; + }); + }, [messages]); return ( <> @@ -432,7 +473,7 @@ export function SessionOutputViewer({ session, onClose, className }: SessionOutp ) : ( <> - {messages.map((message: ClaudeStreamMessage, index: number) => ( + {displayableMessages.map((message: ClaudeStreamMessage, index: number) => ( - {messages.map((message: ClaudeStreamMessage, index: number) => ( + {displayableMessages.map((message: ClaudeStreamMessage, index: number) => ( = ({ message, classNa if (message.type === "assistant" && message.message) { const msg = message.message; - return ( + let renderedSomething = false; + + const renderedCard = (
@@ -114,6 +116,7 @@ const StreamMessageComponent: React.FC = ({ message, classNa ? content.text : (content.text?.text || JSON.stringify(content.text || content)); + renderedSomething = true; return (
= ({ message, classNa const renderToolWidget = () => { // Task tool - for sub-agent tasks if (toolName === "task" && input) { + renderedSomething = true; return ; } // Edit tool if (toolName === "edit" && input?.file_path) { + renderedSomething = true; return ; } // MultiEdit tool if (toolName === "multiedit" && input?.file_path && input?.edits) { + renderedSomething = true; return ; } // MCP tools (starting with mcp__) if (content.name?.startsWith("mcp__")) { + renderedSomething = true; return ; } // TodoWrite tool if (toolName === "todowrite" && input?.todos) { + renderedSomething = true; return ; } // LS tool if (toolName === "ls" && input?.path) { + renderedSomething = true; return ; } // Read tool if (toolName === "read" && input?.file_path) { + renderedSomething = true; return ; } // Glob tool if (toolName === "glob" && input?.pattern) { + renderedSomething = true; return ; } // Bash tool if (toolName === "bash" && input?.command) { + renderedSomething = true; return ; } // Write tool if (toolName === "write" && input?.file_path && input?.content) { + renderedSomething = true; return ; } // Grep tool if (toolName === "grep" && input?.pattern) { + renderedSomething = true; return ; } @@ -217,10 +231,12 @@ const StreamMessageComponent: React.FC = ({ message, classNa // Render the tool widget const widget = renderToolWidget(); if (widget) { + renderedSomething = true; return
{widget}
; } // Fallback to basic tool display + renderedSomething = true; return (
@@ -252,6 +268,8 @@ const StreamMessageComponent: React.FC = ({ message, classNa ); + if (!renderedSomething) return null; + return renderedCard; } // User message @@ -261,12 +279,9 @@ const StreamMessageComponent: React.FC = ({ message, classNa const msg = message.message; - // Skip empty user messages - if (!msg.content || (Array.isArray(msg.content) && msg.content.length === 0)) { - return null; - } + let renderedSomething = false; - return ( + const renderedCard = (
@@ -276,6 +291,8 @@ const StreamMessageComponent: React.FC = ({ message, classNa {typeof msg.content === 'string' && ( (() => { const contentStr = msg.content as string; + if (contentStr.trim() === '') return null; + renderedSomething = true; // Check if it's a command message const commandMatch = contentStr.match(/(.+?)<\/command-name>[\s\S]*?(.+?)<\/command-message>[\s\S]*?(.*?)<\/command-args>/); @@ -310,27 +327,16 @@ const StreamMessageComponent: React.FC = ({ message, classNa {Array.isArray(msg.content) && msg.content.map((content: any, idx: number) => { // Tool result if (content.type === "tool_result") { - // Skip rendering tool results that are already displayed by tool widgets - // We need to check if this result corresponds to a tool that has its own widget - - // Find the corresponding tool use for this result + // Skip duplicate tool_result if a dedicated widget is present let hasCorrespondingWidget = false; if (content.tool_use_id && streamMessages) { - // Look for the matching tool_use in previous assistant messages for (let i = streamMessages.length - 1; i >= 0; i--) { const prevMsg = streamMessages[i]; if (prevMsg.type === 'assistant' && prevMsg.message?.content && Array.isArray(prevMsg.message.content)) { - const toolUse = prevMsg.message.content.find((c: any) => - c.type === 'tool_use' && c.id === content.tool_use_id - ); + const toolUse = prevMsg.message.content.find((c: any) => c.type === 'tool_use' && c.id === content.tool_use_id); if (toolUse) { const toolName = toolUse.name?.toLowerCase(); - // List of tools that display their own results - const toolsWithWidgets = [ - 'task', 'edit', 'multiedit', 'todowrite', 'ls', 'read', - 'glob', 'bash', 'write', 'grep' - ]; - // Also check for MCP tools + const toolsWithWidgets = ['task','edit','multiedit','todowrite','ls','read','glob','bash','write','grep']; if (toolsWithWidgets.includes(toolName) || toolUse.name?.startsWith('mcp__')) { hasCorrespondingWidget = true; } @@ -339,9 +345,8 @@ const StreamMessageComponent: React.FC = ({ message, classNa } } } - - // If the tool has its own widget that displays results, skip rendering the duplicate - if (hasCorrespondingWidget && !content.is_error) { + + if (hasCorrespondingWidget) { return null; } // Extract the actual content string @@ -370,6 +375,7 @@ const StreamMessageComponent: React.FC = ({ message, classNa const beforeReminder = contentText.substring(0, reminderMatch.index || 0).trim(); const afterReminder = contentText.substring((reminderMatch.index || 0) + reminderMatch[0].length).trim(); + renderedSomething = true; return (
@@ -404,6 +410,7 @@ const StreamMessageComponent: React.FC = ({ message, classNa const isEditResult = contentText.includes("has been updated. Here's the result of running `cat -n`"); if (isEditResult) { + renderedSomething = true; return (
@@ -421,6 +428,7 @@ const StreamMessageComponent: React.FC = ({ message, classNa contentText.includes("Applied multiple edits to"); if (isMultiEditResult) { + renderedSomething = true; return (
@@ -470,6 +478,7 @@ const StreamMessageComponent: React.FC = ({ message, classNa })(); if (isLSResult) { + renderedSomething = true; return (
@@ -508,6 +517,7 @@ const StreamMessageComponent: React.FC = ({ message, classNa } } + renderedSomething = true; return (
@@ -521,6 +531,7 @@ const StreamMessageComponent: React.FC = ({ message, classNa // Handle empty tool results if (!contentText || contentText.trim() === '') { + renderedSomething = true; return (
@@ -534,6 +545,7 @@ const StreamMessageComponent: React.FC = ({ message, classNa ); } + renderedSomething = true; return (
@@ -560,6 +572,7 @@ const StreamMessageComponent: React.FC = ({ message, classNa ? content.text : (content.text?.text || JSON.stringify(content.text)); + renderedSomething = true; return (
{textContent} @@ -574,6 +587,8 @@ const StreamMessageComponent: React.FC = ({ message, classNa ); + if (!renderedSomething) return null; + return renderedCard; } // Result message - render with markdown diff --git a/vite.config.ts b/vite.config.ts index bd2d59f..10d92cd 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -3,7 +3,6 @@ import react from "@vitejs/plugin-react"; import tailwindcss from "@tailwindcss/vite"; import { fileURLToPath, URL } from "node:url"; -// @ts-expect-error process is a nodejs global const host = process.env.TAURI_DEV_HOST; // https://vitejs.dev/config/ @@ -38,4 +37,26 @@ export default defineConfig(async () => ({ ignored: ["**/src-tauri/**"], }, }, + + // Build configuration for code splitting + build: { + // Increase chunk size warning limit to 2000 KB + chunkSizeWarningLimit: 2000, + + rollupOptions: { + output: { + // Manual chunks for better code splitting + manualChunks: { + // Vendor chunks + 'react-vendor': ['react', 'react-dom'], + 'ui-vendor': ['@radix-ui/react-dialog', '@radix-ui/react-dropdown-menu', '@radix-ui/react-select', '@radix-ui/react-tabs', '@radix-ui/react-tooltip', '@radix-ui/react-switch', '@radix-ui/react-popover'], + 'editor-vendor': ['@uiw/react-md-editor'], + 'syntax-vendor': ['react-syntax-highlighter'], + // Tauri and other utilities + 'tauri': ['@tauri-apps/api', '@tauri-apps/plugin-dialog', '@tauri-apps/plugin-shell'], + 'utils': ['date-fns', 'clsx', 'tailwind-merge'], + }, + }, + }, + }, }));