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.
This commit is contained in:
Mufeed VH 2025-06-25 00:52:07 +05:30
parent e0a0ddf6ba
commit 5d69b449be
6 changed files with 176 additions and 88 deletions

View file

@ -1952,9 +1952,6 @@ struct GitHubApiResponse {
path: String,
sha: String,
size: i64,
url: String,
html_url: String,
git_url: String,
download_url: Option<String>,
#[serde(rename = "type")]
file_type: String,

View file

@ -103,45 +103,54 @@ export const AgentExecution: React.FC<AgentExecutionProps> = ({
// 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;
}
}

View file

@ -117,52 +117,57 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
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]);

View file

@ -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
) : (
<>
<AnimatePresence>
{messages.map((message: ClaudeStreamMessage, index: number) => (
{displayableMessages.map((message: ClaudeStreamMessage, index: number) => (
<motion.div
key={index}
initial={{ opacity: 0, y: 10 }}
@ -556,7 +597,7 @@ export function SessionOutputViewer({ session, onClose, className }: SessionOutp
) : (
<>
<AnimatePresence>
{messages.map((message: ClaudeStreamMessage, index: number) => (
{displayableMessages.map((message: ClaudeStreamMessage, index: number) => (
<motion.div
key={index}
initial={{ opacity: 0, y: 10 }}

View file

@ -100,7 +100,9 @@ const StreamMessageComponent: React.FC<StreamMessageProps> = ({ message, classNa
if (message.type === "assistant" && message.message) {
const msg = message.message;
return (
let renderedSomething = false;
const renderedCard = (
<Card className={cn("border-primary/20 bg-primary/5", className)}>
<CardContent className="p-4">
<div className="flex items-start gap-3">
@ -114,6 +116,7 @@ const StreamMessageComponent: React.FC<StreamMessageProps> = ({ message, classNa
? content.text
: (content.text?.text || JSON.stringify(content.text || content));
renderedSomething = true;
return (
<div key={idx} className="prose prose-sm dark:prose-invert max-w-none">
<ReactMarkdown
@ -157,56 +160,67 @@ const StreamMessageComponent: React.FC<StreamMessageProps> = ({ message, classNa
const renderToolWidget = () => {
// Task tool - for sub-agent tasks
if (toolName === "task" && input) {
renderedSomething = true;
return <TaskWidget description={input.description} prompt={input.prompt} result={toolResult} />;
}
// Edit tool
if (toolName === "edit" && input?.file_path) {
renderedSomething = true;
return <EditWidget {...input} result={toolResult} />;
}
// MultiEdit tool
if (toolName === "multiedit" && input?.file_path && input?.edits) {
renderedSomething = true;
return <MultiEditWidget {...input} result={toolResult} />;
}
// MCP tools (starting with mcp__)
if (content.name?.startsWith("mcp__")) {
renderedSomething = true;
return <MCPWidget toolName={content.name} input={input} result={toolResult} />;
}
// TodoWrite tool
if (toolName === "todowrite" && input?.todos) {
renderedSomething = true;
return <TodoWidget todos={input.todos} result={toolResult} />;
}
// LS tool
if (toolName === "ls" && input?.path) {
renderedSomething = true;
return <LSWidget path={input.path} result={toolResult} />;
}
// Read tool
if (toolName === "read" && input?.file_path) {
renderedSomething = true;
return <ReadWidget filePath={input.file_path} result={toolResult} />;
}
// Glob tool
if (toolName === "glob" && input?.pattern) {
renderedSomething = true;
return <GlobWidget pattern={input.pattern} result={toolResult} />;
}
// Bash tool
if (toolName === "bash" && input?.command) {
renderedSomething = true;
return <BashWidget command={input.command} description={input.description} result={toolResult} />;
}
// Write tool
if (toolName === "write" && input?.file_path && input?.content) {
renderedSomething = true;
return <WriteWidget filePath={input.file_path} content={input.content} result={toolResult} />;
}
// Grep tool
if (toolName === "grep" && input?.pattern) {
renderedSomething = true;
return <GrepWidget pattern={input.pattern} include={input.include} path={input.path} exclude={input.exclude} result={toolResult} />;
}
@ -217,10 +231,12 @@ const StreamMessageComponent: React.FC<StreamMessageProps> = ({ message, classNa
// Render the tool widget
const widget = renderToolWidget();
if (widget) {
renderedSomething = true;
return <div key={idx}>{widget}</div>;
}
// Fallback to basic tool display
renderedSomething = true;
return (
<div key={idx} className="space-y-2">
<div className="flex items-center gap-2">
@ -252,6 +268,8 @@ const StreamMessageComponent: React.FC<StreamMessageProps> = ({ message, classNa
</CardContent>
</Card>
);
if (!renderedSomething) return null;
return renderedCard;
}
// User message
@ -261,12 +279,9 @@ const StreamMessageComponent: React.FC<StreamMessageProps> = ({ 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 = (
<Card className={cn("border-muted-foreground/20 bg-muted/20", className)}>
<CardContent className="p-4">
<div className="flex items-start gap-3">
@ -276,6 +291,8 @@ const StreamMessageComponent: React.FC<StreamMessageProps> = ({ 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>(.+?)<\/command-name>[\s\S]*?<command-message>(.+?)<\/command-message>[\s\S]*?<command-args>(.*?)<\/command-args>/);
@ -310,27 +327,16 @@ const StreamMessageComponent: React.FC<StreamMessageProps> = ({ 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<StreamMessageProps> = ({ 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<StreamMessageProps> = ({ 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 (
<div key={idx} className="space-y-2">
<div className="flex items-center gap-2">
@ -404,6 +410,7 @@ const StreamMessageComponent: React.FC<StreamMessageProps> = ({ message, classNa
const isEditResult = contentText.includes("has been updated. Here's the result of running `cat -n`");
if (isEditResult) {
renderedSomething = true;
return (
<div key={idx} className="space-y-2">
<div className="flex items-center gap-2">
@ -421,6 +428,7 @@ const StreamMessageComponent: React.FC<StreamMessageProps> = ({ message, classNa
contentText.includes("Applied multiple edits to");
if (isMultiEditResult) {
renderedSomething = true;
return (
<div key={idx} className="space-y-2">
<div className="flex items-center gap-2">
@ -470,6 +478,7 @@ const StreamMessageComponent: React.FC<StreamMessageProps> = ({ message, classNa
})();
if (isLSResult) {
renderedSomething = true;
return (
<div key={idx} className="space-y-2">
<div className="flex items-center gap-2">
@ -508,6 +517,7 @@ const StreamMessageComponent: React.FC<StreamMessageProps> = ({ message, classNa
}
}
renderedSomething = true;
return (
<div key={idx} className="space-y-2">
<div className="flex items-center gap-2">
@ -521,6 +531,7 @@ const StreamMessageComponent: React.FC<StreamMessageProps> = ({ message, classNa
// Handle empty tool results
if (!contentText || contentText.trim() === '') {
renderedSomething = true;
return (
<div key={idx} className="space-y-2">
<div className="flex items-center gap-2">
@ -534,6 +545,7 @@ const StreamMessageComponent: React.FC<StreamMessageProps> = ({ message, classNa
);
}
renderedSomething = true;
return (
<div key={idx} className="space-y-2">
<div className="flex items-center gap-2">
@ -560,6 +572,7 @@ const StreamMessageComponent: React.FC<StreamMessageProps> = ({ message, classNa
? content.text
: (content.text?.text || JSON.stringify(content.text));
renderedSomething = true;
return (
<div key={idx} className="text-sm">
{textContent}
@ -574,6 +587,8 @@ const StreamMessageComponent: React.FC<StreamMessageProps> = ({ message, classNa
</CardContent>
</Card>
);
if (!renderedSomething) return null;
return renderedCard;
}
// Result message - render with markdown

View file

@ -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'],
},
},
},
},
}));