Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions src/web/chat/components/ToolRendering/ToolCollapse.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React, { useState } from 'react';
import { CornerDownRight } from 'lucide-react';
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/web/chat/components/ui/collapsible';

interface ToolCollapseProps {
summaryText: string;
defaultExpanded?: boolean;
children: React.ReactNode;
ariaLabel?: string;
}

export function ToolCollapse({
summaryText,
defaultExpanded = false,
children,
ariaLabel
}: ToolCollapseProps) {
const [isExpanded, setIsExpanded] = useState(defaultExpanded);

return (
<div className="flex flex-col gap-1 -mt-0.5">
<Collapsible open={isExpanded} onOpenChange={setIsExpanded}>
<CollapsibleTrigger asChild>
<div
className="text-sm text-muted-foreground cursor-pointer select-none hover:text-foreground flex items-center gap-1"
aria-label={ariaLabel || `Toggle ${summaryText.toLowerCase()} details`}
>
<CornerDownRight
size={12}
className={`transition-transform ${isExpanded ? 'rotate-90' : ''}`}
/>
{summaryText}
</div>
</CollapsibleTrigger>

<CollapsibleContent>
{children}
</CollapsibleContent>
</Collapsible>
</div>
);
}
9 changes: 7 additions & 2 deletions src/web/chat/components/ToolRendering/tools/BashTool.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import { CodeHighlight } from '../../CodeHighlight';
import { ToolCollapse } from '../ToolCollapse';

interface BashToolProps {
input: any;
Expand All @@ -9,13 +10,17 @@ interface BashToolProps {

export function BashTool({ input, result }: BashToolProps) {
return (
<div className="flex flex-col gap-1 -mt-0.5">
<ToolCollapse
summaryText="Command output"
defaultExpanded={true}
ariaLabel="Toggle command output"
>
<CodeHighlight
code={result || '(No content)'}
language="text"
showLineNumbers={false}
className="bg-neutral-950 rounded-xl overflow-hidden"
/>
</div>
</ToolCollapse>
);
}
85 changes: 46 additions & 39 deletions src/web/chat/components/ToolRendering/tools/EditTool.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react';
import { detectLanguageFromPath } from '../../../utils/language-detection';
import { CodeHighlight } from '../../CodeHighlight';
import { DiffViewer } from './DiffViewer';
import { ToolCollapse } from '../ToolCollapse';

interface EditToolProps {
input: any;
Expand All @@ -14,54 +15,60 @@ export function EditTool({ input, result, isMultiEdit = false, workingDirectory
// 从文件路径检测语言
const filePath = input?.file_path || '';
const language = detectLanguageFromPath(filePath);
// For MultiEdit, process all edits
if (isMultiEdit && input.edits && Array.isArray(input.edits)) {
return (
<div className="flex flex-col gap-1 -mt-0.5">
{input.edits.map((edit: any, index: number) => (
<div key={index}>
<DiffViewer
oldValue={edit.old_string || ''}
newValue={edit.new_string || ''}
language={language}
/>
{index < input.edits.length - 1 && (
<div className="h-2" />
)}
</div>
))}
</div>
);
}

const renderContent = () => {
// For MultiEdit, process all edits
if (isMultiEdit && input.edits && Array.isArray(input.edits)) {
return (
<div className="flex flex-col gap-1">
{input.edits.map((edit: any, index: number) => (
<div key={index}>
<DiffViewer
oldValue={edit.old_string || ''}
newValue={edit.new_string || ''}
language={language}
/>
{index < input.edits.length - 1 && (
<div className="h-2" />
)}
</div>
))}
</div>
);
}

// For regular Edit, process single edit
if (input.old_string !== undefined && input.new_string !== undefined) {
return (
<div className="flex flex-col gap-1 -mt-0.5">
// For regular Edit, process single edit
if (input.old_string !== undefined && input.new_string !== undefined) {
return (
<DiffViewer
oldValue={input.old_string}
newValue={input.new_string}
language={language}
/>
);
}

// Fallback if we can't parse the edit
return result ? (
<CodeHighlight
code={result}
language={language}
className="bg-neutral-950 rounded-xl overflow-hidden"
/>
) : (
<div className="bg-neutral-950 rounded-xl overflow-hidden">
<pre className="p-3 m-0 text-neutral-200 font-mono text-xs leading-6">Edit completed successfully</pre>
</div>
);
}
};

// Fallback if we can't parse the edit

return (
<div className="flex flex-col gap-1 -mt-0.5">
{result ? (
<CodeHighlight
code={result}
language={language}
className="bg-neutral-950 rounded-xl overflow-hidden"
/>
) : (
<div className="bg-neutral-950 rounded-xl overflow-hidden">
<pre className="p-3 m-0 text-neutral-200 font-mono text-xs leading-6">Edit completed successfully</pre>
</div>
)}
</div>
<ToolCollapse
summaryText="File changes"
defaultExpanded={true}
ariaLabel="Toggle file changes"
>
{renderContent()}
</ToolCollapse>
);
}
64 changes: 25 additions & 39 deletions src/web/chat/components/ToolRendering/tools/FallbackTool.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React, { useState } from 'react';
import { CornerDownRight } from 'lucide-react';
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/web/chat/components/ui/collapsible';
import React from 'react';
import { ToolCollapse } from '../ToolCollapse';

interface FallbackToolProps {
toolName: string;
Expand All @@ -9,8 +8,6 @@ interface FallbackToolProps {
}

export function FallbackTool({ toolName, input, result }: FallbackToolProps) {
const [isExpanded, setIsExpanded] = useState(false);

const formatContent = (content: string): string => {
try {
// Try to parse and format as JSON if possible
Expand All @@ -23,41 +20,30 @@ export function FallbackTool({ toolName, input, result }: FallbackToolProps) {
};

return (
<div className="flex flex-col gap-1 -mt-0.5">
<Collapsible open={isExpanded} onOpenChange={setIsExpanded}>
<CollapsibleTrigger asChild>
<div
className="text-sm text-muted-foreground cursor-pointer select-none hover:text-foreground flex items-center gap-1"
aria-label={`Toggle ${toolName} details`}
>
<CornerDownRight
size={12}
className={`transition-transform ${isExpanded ? 'rotate-90' : ''}`}
/>
{toolName} completed
<ToolCollapse
summaryText={`${toolName} completed`}
defaultExpanded={false}
ariaLabel={`Toggle ${toolName} details`}
>
<div className="space-y-1">
{result && (
<div className="bg-neutral-950 rounded-xl overflow-hidden">
<pre className="m-0 p-3 text-neutral-100 font-mono text-xs leading-relaxed whitespace-pre-wrap break-words">
{formatContent(result || 'No result')}
</pre>
</div>
</CollapsibleTrigger>
)}

<CollapsibleContent className="space-y-1">
{result && (
<div className="bg-neutral-950 rounded-xl overflow-hidden">
<pre className="m-0 p-3 text-neutral-100 font-mono text-xs leading-relaxed whitespace-pre-wrap break-words">
{formatContent(result || 'No result')}
</pre>
</div>
)}

{/* Always show input in expanded state for debugging */}
{input && (
<div className="bg-secondary rounded-xl p-3 overflow-x-auto font-mono text-xs leading-relaxed">
<strong className="text-foreground">Input:</strong>
<pre className="m-0 mt-1 whitespace-pre-wrap break-words">
{JSON.stringify(input, null, 2)}
</pre>
</div>
)}
</CollapsibleContent>
</Collapsible>
</div>
{/* Always show input in expanded state for debugging */}
{input && (
<div className="bg-secondary rounded-xl p-3 overflow-x-auto font-mono text-xs leading-relaxed">
<strong className="text-foreground">Input:</strong>
<pre className="m-0 mt-1 whitespace-pre-wrap break-words">
{JSON.stringify(input, null, 2)}
</pre>
</div>
)}
</div>
</ToolCollapse>
);
}
9 changes: 7 additions & 2 deletions src/web/chat/components/ToolRendering/tools/PlanTool.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import ReactMarkdown from 'react-markdown';
import { ToolCollapse } from '../ToolCollapse';

interface PlanToolProps {
input: any;
Expand All @@ -11,7 +12,11 @@ export function PlanTool({ input, result }: PlanToolProps) {
const planContent = input.plan || result || 'No plan provided';

return (
<div className="flex flex-col gap-1 -mt-0.5">
<ToolCollapse
summaryText="Implementation plan"
defaultExpanded={true}
ariaLabel="Toggle implementation plan"
>
<div className="bg-secondary rounded-xl p-4 mt-1 border-l-3 border-accent prose prose-sm prose-neutral dark:prose-invert max-w-none">
<ReactMarkdown
components={{
Expand All @@ -30,6 +35,6 @@ export function PlanTool({ input, result }: PlanToolProps) {
{planContent}
</ReactMarkdown>
</div>
</div>
</ToolCollapse>
);
}
43 changes: 16 additions & 27 deletions src/web/chat/components/ToolRendering/tools/ReadTool.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import React, { useState } from 'react';
import { CornerDownRight } from 'lucide-react';
import React from 'react';
import { countLines } from '../../../utils/tool-utils';
import { detectLanguageFromPath } from '../../../utils/language-detection';
import { CodeHighlight } from '../../CodeHighlight';
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/web/chat/components/ui/collapsible';
import { ToolCollapse } from '../ToolCollapse';

interface ReadToolProps {
input: any;
Expand All @@ -27,35 +26,25 @@ export function ReadTool({ input, result, workingDirectory }: ReadToolProps) {
return <div />;
}

const [isExpanded, setIsExpanded] = useState(false);

const cleanedContent = cleanFileContent(result);
const lineCount = countLines(cleanedContent);
const filePath = input?.file_path || '';
const language = detectLanguageFromPath(filePath);

return (
<div className="flex flex-col gap-1 -mt-0.5">
<Collapsible open={isExpanded} onOpenChange={setIsExpanded}>
<CollapsibleTrigger className="flex items-center gap-1 text-sm text-muted-foreground cursor-pointer select-none hover:text-foreground" aria-label="Toggle file content">
<CornerDownRight
size={12}
className={`transition-transform ${isExpanded ? 'rotate-90' : ''}`}
/>
Read {lineCount} line{lineCount !== 1 ? 's' : ''}
</CollapsibleTrigger>

<CollapsibleContent>
{cleanedContent && (
<CodeHighlight
code={cleanedContent}
language={language}
showLineNumbers={true}
className="bg-neutral-950 rounded-xl overflow-hidden mt-1"
/>
)}
</CollapsibleContent>
</Collapsible>
</div>
<ToolCollapse
summaryText={`Read ${lineCount} line${lineCount !== 1 ? 's' : ''}`}
defaultExpanded={false}
ariaLabel="Toggle file content"
>
{cleanedContent && (
<CodeHighlight
code={cleanedContent}
language={language}
showLineNumbers={true}
className="bg-neutral-950 rounded-xl overflow-hidden mt-1"
/>
)}
</ToolCollapse>
);
}
Loading