mirror of
https://github.com/getAsterisk/claudia.git
synced 2025-07-07 18:15:00 +00:00
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:
parent
e0a0ddf6ba
commit
5d69b449be
6 changed files with 176 additions and 88 deletions
|
@ -1952,9 +1952,6 @@ struct GitHubApiResponse {
|
||||||
path: String,
|
path: String,
|
||||||
sha: String,
|
sha: String,
|
||||||
size: i64,
|
size: i64,
|
||||||
url: String,
|
|
||||||
html_url: String,
|
|
||||||
git_url: String,
|
|
||||||
download_url: Option<String>,
|
download_url: Option<String>,
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
file_type: String,
|
file_type: String,
|
||||||
|
|
|
@ -103,45 +103,54 @@ export const AgentExecution: React.FC<AgentExecutionProps> = ({
|
||||||
|
|
||||||
// Skip empty user messages
|
// Skip empty user messages
|
||||||
if (message.type === "user" && message.message) {
|
if (message.type === "user" && message.message) {
|
||||||
|
if (message.isMeta) return false;
|
||||||
|
|
||||||
const msg = message.message;
|
const msg = message.message;
|
||||||
if (!msg.content || (Array.isArray(msg.content) && msg.content.length === 0)) {
|
if (!msg.content || (Array.isArray(msg.content) && msg.content.length === 0)) {
|
||||||
return false;
|
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)) {
|
if (Array.isArray(msg.content)) {
|
||||||
const hasOnlyHiddenToolResults = msg.content.every((content: any) => {
|
let hasVisibleContent = false;
|
||||||
if (content.type !== "tool_result") return false;
|
for (const content of msg.content) {
|
||||||
|
if (content.type === "text") {
|
||||||
// Check if this tool result should be hidden
|
hasVisibleContent = true;
|
||||||
let hasCorrespondingWidget = false;
|
break;
|
||||||
if (content.tool_use_id) {
|
} else if (content.type === "tool_result") {
|
||||||
// Look for the matching tool_use in previous assistant messages
|
// Check if this tool result will be skipped by a widget
|
||||||
for (let i = index - 1; i >= 0; i--) {
|
let willBeSkipped = false;
|
||||||
const prevMsg = messages[i];
|
if (content.tool_use_id) {
|
||||||
if (prevMsg.type === 'assistant' && prevMsg.message?.content && Array.isArray(prevMsg.message.content)) {
|
// Look for the matching tool_use in previous assistant messages
|
||||||
const toolUse = prevMsg.message.content.find((c: any) =>
|
for (let i = index - 1; i >= 0; i--) {
|
||||||
c.type === 'tool_use' && c.id === content.tool_use_id
|
const prevMsg = messages[i];
|
||||||
);
|
if (prevMsg.type === 'assistant' && prevMsg.message?.content && Array.isArray(prevMsg.message.content)) {
|
||||||
if (toolUse) {
|
const toolUse = prevMsg.message.content.find((c: any) =>
|
||||||
const toolName = toolUse.name?.toLowerCase();
|
c.type === 'tool_use' && c.id === content.tool_use_id
|
||||||
const toolsWithWidgets = [
|
);
|
||||||
'task', 'edit', 'multiedit', 'todowrite', 'ls', 'read',
|
if (toolUse) {
|
||||||
'glob', 'bash', 'write', 'grep'
|
const toolName = toolUse.name?.toLowerCase();
|
||||||
];
|
const toolsWithWidgets = [
|
||||||
if (toolsWithWidgets.includes(toolName) || toolUse.name?.startsWith('mcp__')) {
|
'task', 'edit', 'multiedit', 'todowrite', 'ls', 'read',
|
||||||
hasCorrespondingWidget = true;
|
'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 false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -117,52 +117,57 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
|
||||||
return false;
|
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.type === "user" && message.message) {
|
||||||
|
if (message.isMeta) return false;
|
||||||
|
|
||||||
const msg = message.message;
|
const msg = message.message;
|
||||||
if (!msg.content || (Array.isArray(msg.content) && msg.content.length === 0)) {
|
if (!msg.content || (Array.isArray(msg.content) && msg.content.length === 0)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if this is a user message with only tool results that are already displayed
|
|
||||||
if (Array.isArray(msg.content)) {
|
if (Array.isArray(msg.content)) {
|
||||||
const hasOnlyHiddenToolResults = msg.content.every((content: any) => {
|
let hasVisibleContent = false;
|
||||||
if (content.type !== "tool_result") return false;
|
for (const content of msg.content) {
|
||||||
|
if (content.type === "text") {
|
||||||
// Check if this tool result should be hidden
|
hasVisibleContent = true;
|
||||||
let hasCorrespondingWidget = false;
|
break;
|
||||||
if (content.tool_use_id) {
|
}
|
||||||
// Look for the matching tool_use in previous assistant messages
|
if (content.type === "tool_result") {
|
||||||
for (let i = index - 1; i >= 0; i--) {
|
let willBeSkipped = false;
|
||||||
const prevMsg = messages[i];
|
if (content.tool_use_id) {
|
||||||
if (prevMsg.type === 'assistant' && prevMsg.message?.content && Array.isArray(prevMsg.message.content)) {
|
// Look for the matching tool_use in previous assistant messages
|
||||||
const toolUse = prevMsg.message.content.find((c: any) =>
|
for (let i = index - 1; i >= 0; i--) {
|
||||||
c.type === 'tool_use' && c.id === content.tool_use_id
|
const prevMsg = messages[i];
|
||||||
);
|
if (prevMsg.type === 'assistant' && prevMsg.message?.content && Array.isArray(prevMsg.message.content)) {
|
||||||
if (toolUse) {
|
const toolUse = prevMsg.message.content.find((c: any) =>
|
||||||
const toolName = toolUse.name?.toLowerCase();
|
c.type === 'tool_use' && c.id === content.tool_use_id
|
||||||
const toolsWithWidgets = [
|
);
|
||||||
'task', 'edit', 'multiedit', 'todowrite', 'ls', 'read',
|
if (toolUse) {
|
||||||
'glob', 'bash', 'write', 'grep'
|
const toolName = toolUse.name?.toLowerCase();
|
||||||
];
|
const toolsWithWidgets = [
|
||||||
if (toolsWithWidgets.includes(toolName) || toolUse.name?.startsWith('mcp__')) {
|
'task', 'edit', 'multiedit', 'todowrite', 'ls', 'read',
|
||||||
hasCorrespondingWidget = true;
|
'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 (!hasVisibleContent) {
|
||||||
});
|
|
||||||
|
|
||||||
if (hasOnlyHiddenToolResults) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}, [messages]);
|
}, [messages]);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { useState, useEffect, useRef } from 'react';
|
import { useState, useEffect, useRef, useMemo } from 'react';
|
||||||
import { motion, AnimatePresence } from 'framer-motion';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
import { X, Maximize2, Minimize2, Copy, RefreshCw, RotateCcw, ChevronDown } from 'lucide-react';
|
import { X, Maximize2, Minimize2, Copy, RefreshCw, RotateCcw, ChevronDown } from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
|
@ -282,6 +282,47 @@ export function SessionOutputViewer({ session, onClose, className }: SessionOutp
|
||||||
loadOutput();
|
loadOutput();
|
||||||
}, [session.id]);
|
}, [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 (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -432,7 +473,7 @@ export function SessionOutputViewer({ session, onClose, className }: SessionOutp
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{messages.map((message: ClaudeStreamMessage, index: number) => (
|
{displayableMessages.map((message: ClaudeStreamMessage, index: number) => (
|
||||||
<motion.div
|
<motion.div
|
||||||
key={index}
|
key={index}
|
||||||
initial={{ opacity: 0, y: 10 }}
|
initial={{ opacity: 0, y: 10 }}
|
||||||
|
@ -556,7 +597,7 @@ export function SessionOutputViewer({ session, onClose, className }: SessionOutp
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{messages.map((message: ClaudeStreamMessage, index: number) => (
|
{displayableMessages.map((message: ClaudeStreamMessage, index: number) => (
|
||||||
<motion.div
|
<motion.div
|
||||||
key={index}
|
key={index}
|
||||||
initial={{ opacity: 0, y: 10 }}
|
initial={{ opacity: 0, y: 10 }}
|
||||||
|
|
|
@ -100,7 +100,9 @@ const StreamMessageComponent: React.FC<StreamMessageProps> = ({ message, classNa
|
||||||
if (message.type === "assistant" && message.message) {
|
if (message.type === "assistant" && message.message) {
|
||||||
const msg = message.message;
|
const msg = message.message;
|
||||||
|
|
||||||
return (
|
let renderedSomething = false;
|
||||||
|
|
||||||
|
const renderedCard = (
|
||||||
<Card className={cn("border-primary/20 bg-primary/5", className)}>
|
<Card className={cn("border-primary/20 bg-primary/5", className)}>
|
||||||
<CardContent className="p-4">
|
<CardContent className="p-4">
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
|
@ -114,6 +116,7 @@ const StreamMessageComponent: React.FC<StreamMessageProps> = ({ message, classNa
|
||||||
? content.text
|
? content.text
|
||||||
: (content.text?.text || JSON.stringify(content.text || content));
|
: (content.text?.text || JSON.stringify(content.text || content));
|
||||||
|
|
||||||
|
renderedSomething = true;
|
||||||
return (
|
return (
|
||||||
<div key={idx} className="prose prose-sm dark:prose-invert max-w-none">
|
<div key={idx} className="prose prose-sm dark:prose-invert max-w-none">
|
||||||
<ReactMarkdown
|
<ReactMarkdown
|
||||||
|
@ -157,56 +160,67 @@ const StreamMessageComponent: React.FC<StreamMessageProps> = ({ message, classNa
|
||||||
const renderToolWidget = () => {
|
const renderToolWidget = () => {
|
||||||
// Task tool - for sub-agent tasks
|
// Task tool - for sub-agent tasks
|
||||||
if (toolName === "task" && input) {
|
if (toolName === "task" && input) {
|
||||||
|
renderedSomething = true;
|
||||||
return <TaskWidget description={input.description} prompt={input.prompt} result={toolResult} />;
|
return <TaskWidget description={input.description} prompt={input.prompt} result={toolResult} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Edit tool
|
// Edit tool
|
||||||
if (toolName === "edit" && input?.file_path) {
|
if (toolName === "edit" && input?.file_path) {
|
||||||
|
renderedSomething = true;
|
||||||
return <EditWidget {...input} result={toolResult} />;
|
return <EditWidget {...input} result={toolResult} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
// MultiEdit tool
|
// MultiEdit tool
|
||||||
if (toolName === "multiedit" && input?.file_path && input?.edits) {
|
if (toolName === "multiedit" && input?.file_path && input?.edits) {
|
||||||
|
renderedSomething = true;
|
||||||
return <MultiEditWidget {...input} result={toolResult} />;
|
return <MultiEditWidget {...input} result={toolResult} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
// MCP tools (starting with mcp__)
|
// MCP tools (starting with mcp__)
|
||||||
if (content.name?.startsWith("mcp__")) {
|
if (content.name?.startsWith("mcp__")) {
|
||||||
|
renderedSomething = true;
|
||||||
return <MCPWidget toolName={content.name} input={input} result={toolResult} />;
|
return <MCPWidget toolName={content.name} input={input} result={toolResult} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TodoWrite tool
|
// TodoWrite tool
|
||||||
if (toolName === "todowrite" && input?.todos) {
|
if (toolName === "todowrite" && input?.todos) {
|
||||||
|
renderedSomething = true;
|
||||||
return <TodoWidget todos={input.todos} result={toolResult} />;
|
return <TodoWidget todos={input.todos} result={toolResult} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
// LS tool
|
// LS tool
|
||||||
if (toolName === "ls" && input?.path) {
|
if (toolName === "ls" && input?.path) {
|
||||||
|
renderedSomething = true;
|
||||||
return <LSWidget path={input.path} result={toolResult} />;
|
return <LSWidget path={input.path} result={toolResult} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read tool
|
// Read tool
|
||||||
if (toolName === "read" && input?.file_path) {
|
if (toolName === "read" && input?.file_path) {
|
||||||
|
renderedSomething = true;
|
||||||
return <ReadWidget filePath={input.file_path} result={toolResult} />;
|
return <ReadWidget filePath={input.file_path} result={toolResult} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Glob tool
|
// Glob tool
|
||||||
if (toolName === "glob" && input?.pattern) {
|
if (toolName === "glob" && input?.pattern) {
|
||||||
|
renderedSomething = true;
|
||||||
return <GlobWidget pattern={input.pattern} result={toolResult} />;
|
return <GlobWidget pattern={input.pattern} result={toolResult} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bash tool
|
// Bash tool
|
||||||
if (toolName === "bash" && input?.command) {
|
if (toolName === "bash" && input?.command) {
|
||||||
|
renderedSomething = true;
|
||||||
return <BashWidget command={input.command} description={input.description} result={toolResult} />;
|
return <BashWidget command={input.command} description={input.description} result={toolResult} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write tool
|
// Write tool
|
||||||
if (toolName === "write" && input?.file_path && input?.content) {
|
if (toolName === "write" && input?.file_path && input?.content) {
|
||||||
|
renderedSomething = true;
|
||||||
return <WriteWidget filePath={input.file_path} content={input.content} result={toolResult} />;
|
return <WriteWidget filePath={input.file_path} content={input.content} result={toolResult} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Grep tool
|
// Grep tool
|
||||||
if (toolName === "grep" && input?.pattern) {
|
if (toolName === "grep" && input?.pattern) {
|
||||||
|
renderedSomething = true;
|
||||||
return <GrepWidget pattern={input.pattern} include={input.include} path={input.path} exclude={input.exclude} result={toolResult} />;
|
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
|
// Render the tool widget
|
||||||
const widget = renderToolWidget();
|
const widget = renderToolWidget();
|
||||||
if (widget) {
|
if (widget) {
|
||||||
|
renderedSomething = true;
|
||||||
return <div key={idx}>{widget}</div>;
|
return <div key={idx}>{widget}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback to basic tool display
|
// Fallback to basic tool display
|
||||||
|
renderedSomething = true;
|
||||||
return (
|
return (
|
||||||
<div key={idx} className="space-y-2">
|
<div key={idx} className="space-y-2">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
@ -252,6 +268,8 @@ const StreamMessageComponent: React.FC<StreamMessageProps> = ({ message, classNa
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
if (!renderedSomething) return null;
|
||||||
|
return renderedCard;
|
||||||
}
|
}
|
||||||
|
|
||||||
// User message
|
// User message
|
||||||
|
@ -261,12 +279,9 @@ const StreamMessageComponent: React.FC<StreamMessageProps> = ({ message, classNa
|
||||||
|
|
||||||
const msg = message.message;
|
const msg = message.message;
|
||||||
|
|
||||||
// Skip empty user messages
|
let renderedSomething = false;
|
||||||
if (!msg.content || (Array.isArray(msg.content) && msg.content.length === 0)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
const renderedCard = (
|
||||||
<Card className={cn("border-muted-foreground/20 bg-muted/20", className)}>
|
<Card className={cn("border-muted-foreground/20 bg-muted/20", className)}>
|
||||||
<CardContent className="p-4">
|
<CardContent className="p-4">
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
|
@ -276,6 +291,8 @@ const StreamMessageComponent: React.FC<StreamMessageProps> = ({ message, classNa
|
||||||
{typeof msg.content === 'string' && (
|
{typeof msg.content === 'string' && (
|
||||||
(() => {
|
(() => {
|
||||||
const contentStr = msg.content as string;
|
const contentStr = msg.content as string;
|
||||||
|
if (contentStr.trim() === '') return null;
|
||||||
|
renderedSomething = true;
|
||||||
|
|
||||||
// Check if it's a command message
|
// 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>/);
|
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) => {
|
{Array.isArray(msg.content) && msg.content.map((content: any, idx: number) => {
|
||||||
// Tool result
|
// Tool result
|
||||||
if (content.type === "tool_result") {
|
if (content.type === "tool_result") {
|
||||||
// Skip rendering tool results that are already displayed by tool widgets
|
// Skip duplicate tool_result if a dedicated widget is present
|
||||||
// We need to check if this result corresponds to a tool that has its own widget
|
|
||||||
|
|
||||||
// Find the corresponding tool use for this result
|
|
||||||
let hasCorrespondingWidget = false;
|
let hasCorrespondingWidget = false;
|
||||||
if (content.tool_use_id && streamMessages) {
|
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--) {
|
for (let i = streamMessages.length - 1; i >= 0; i--) {
|
||||||
const prevMsg = streamMessages[i];
|
const prevMsg = streamMessages[i];
|
||||||
if (prevMsg.type === 'assistant' && prevMsg.message?.content && Array.isArray(prevMsg.message.content)) {
|
if (prevMsg.type === 'assistant' && prevMsg.message?.content && Array.isArray(prevMsg.message.content)) {
|
||||||
const toolUse = prevMsg.message.content.find((c: any) =>
|
const toolUse = prevMsg.message.content.find((c: any) => c.type === 'tool_use' && c.id === content.tool_use_id);
|
||||||
c.type === 'tool_use' && c.id === content.tool_use_id
|
|
||||||
);
|
|
||||||
if (toolUse) {
|
if (toolUse) {
|
||||||
const toolName = toolUse.name?.toLowerCase();
|
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'];
|
||||||
const toolsWithWidgets = [
|
|
||||||
'task', 'edit', 'multiedit', 'todowrite', 'ls', 'read',
|
|
||||||
'glob', 'bash', 'write', 'grep'
|
|
||||||
];
|
|
||||||
// Also check for MCP tools
|
|
||||||
if (toolsWithWidgets.includes(toolName) || toolUse.name?.startsWith('mcp__')) {
|
if (toolsWithWidgets.includes(toolName) || toolUse.name?.startsWith('mcp__')) {
|
||||||
hasCorrespondingWidget = true;
|
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) {
|
||||||
if (hasCorrespondingWidget && !content.is_error) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
// Extract the actual content string
|
// 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 beforeReminder = contentText.substring(0, reminderMatch.index || 0).trim();
|
||||||
const afterReminder = contentText.substring((reminderMatch.index || 0) + reminderMatch[0].length).trim();
|
const afterReminder = contentText.substring((reminderMatch.index || 0) + reminderMatch[0].length).trim();
|
||||||
|
|
||||||
|
renderedSomething = true;
|
||||||
return (
|
return (
|
||||||
<div key={idx} className="space-y-2">
|
<div key={idx} className="space-y-2">
|
||||||
<div className="flex items-center gap-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`");
|
const isEditResult = contentText.includes("has been updated. Here's the result of running `cat -n`");
|
||||||
|
|
||||||
if (isEditResult) {
|
if (isEditResult) {
|
||||||
|
renderedSomething = true;
|
||||||
return (
|
return (
|
||||||
<div key={idx} className="space-y-2">
|
<div key={idx} className="space-y-2">
|
||||||
<div className="flex items-center gap-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");
|
contentText.includes("Applied multiple edits to");
|
||||||
|
|
||||||
if (isMultiEditResult) {
|
if (isMultiEditResult) {
|
||||||
|
renderedSomething = true;
|
||||||
return (
|
return (
|
||||||
<div key={idx} className="space-y-2">
|
<div key={idx} className="space-y-2">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
@ -470,6 +478,7 @@ const StreamMessageComponent: React.FC<StreamMessageProps> = ({ message, classNa
|
||||||
})();
|
})();
|
||||||
|
|
||||||
if (isLSResult) {
|
if (isLSResult) {
|
||||||
|
renderedSomething = true;
|
||||||
return (
|
return (
|
||||||
<div key={idx} className="space-y-2">
|
<div key={idx} className="space-y-2">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
@ -508,6 +517,7 @@ const StreamMessageComponent: React.FC<StreamMessageProps> = ({ message, classNa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderedSomething = true;
|
||||||
return (
|
return (
|
||||||
<div key={idx} className="space-y-2">
|
<div key={idx} className="space-y-2">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
@ -521,6 +531,7 @@ const StreamMessageComponent: React.FC<StreamMessageProps> = ({ message, classNa
|
||||||
|
|
||||||
// Handle empty tool results
|
// Handle empty tool results
|
||||||
if (!contentText || contentText.trim() === '') {
|
if (!contentText || contentText.trim() === '') {
|
||||||
|
renderedSomething = true;
|
||||||
return (
|
return (
|
||||||
<div key={idx} className="space-y-2">
|
<div key={idx} className="space-y-2">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
@ -534,6 +545,7 @@ const StreamMessageComponent: React.FC<StreamMessageProps> = ({ message, classNa
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderedSomething = true;
|
||||||
return (
|
return (
|
||||||
<div key={idx} className="space-y-2">
|
<div key={idx} className="space-y-2">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
@ -560,6 +572,7 @@ const StreamMessageComponent: React.FC<StreamMessageProps> = ({ message, classNa
|
||||||
? content.text
|
? content.text
|
||||||
: (content.text?.text || JSON.stringify(content.text));
|
: (content.text?.text || JSON.stringify(content.text));
|
||||||
|
|
||||||
|
renderedSomething = true;
|
||||||
return (
|
return (
|
||||||
<div key={idx} className="text-sm">
|
<div key={idx} className="text-sm">
|
||||||
{textContent}
|
{textContent}
|
||||||
|
@ -574,6 +587,8 @@ const StreamMessageComponent: React.FC<StreamMessageProps> = ({ message, classNa
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
if (!renderedSomething) return null;
|
||||||
|
return renderedCard;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Result message - render with markdown
|
// Result message - render with markdown
|
||||||
|
|
|
@ -3,7 +3,6 @@ import react from "@vitejs/plugin-react";
|
||||||
import tailwindcss from "@tailwindcss/vite";
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
import { fileURLToPath, URL } from "node:url";
|
import { fileURLToPath, URL } from "node:url";
|
||||||
|
|
||||||
// @ts-expect-error process is a nodejs global
|
|
||||||
const host = process.env.TAURI_DEV_HOST;
|
const host = process.env.TAURI_DEV_HOST;
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
|
@ -38,4 +37,26 @@ export default defineConfig(async () => ({
|
||||||
ignored: ["**/src-tauri/**"],
|
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'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue