import React, { useState } from 'react'; import { FileDiff, CheckCircle, AlertTriangle, CircleDashed, Loader2, File, ChevronDown, ChevronUp, Minus, Plus, } from 'lucide-react'; import { cn } from '@/lib/utils'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { ScrollArea } from "@/components/ui/scroll-area"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import { Button } from '@/components/ui/button'; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { LineDiff, DiffStats, extractFromNewFormat, extractFromLegacyFormat, generateLineDiff, generateCharDiff, calculateDiffStats } from './_utils'; import { extractFilePath, extractStrReplaceContent, extractToolData, formatTimestamp, getToolTitle } from '../utils'; import { ToolViewProps } from '../types'; import { LoadingState } from '../shared/LoadingState'; const UnifiedDiffView: React.FC<{ lineDiff: LineDiff[] }> = ({ lineDiff }) => (
{lineDiff.map((line, i) => ( ))}
{line.lineNumber} {line.type === 'removed' && } {line.type === 'added' && }
{line.type === 'removed' && {line.oldLine}} {line.type === 'added' && {line.newLine}} {line.type === 'unchanged' && {line.oldLine}}
); const SplitDiffView: React.FC<{ lineDiff: LineDiff[] }> = ({ lineDiff }) => (
{lineDiff.map((line, i) => ( ))}
Removed Added
{line.oldLine !== null ? (
{line.lineNumber}
{line.type === 'removed' && }
{line.oldLine}
) : null}
{line.newLine !== null ? (
{line.lineNumber}
{line.type === 'added' && }
{line.newLine}
) : null}
); const ErrorState: React.FC = () => (

Invalid String Replacement

Could not extract the old string and new string from the request.

); export function StrReplaceToolView({ name = 'str-replace', assistantContent, toolContent, assistantTimestamp, toolTimestamp, isSuccess = true, isStreaming = false, }: ToolViewProps): JSX.Element { const [expanded, setExpanded] = useState(true); const [viewMode, setViewMode] = useState<'unified' | 'split'>('unified'); let filePath: string | null = null; let oldStr: string | null = null; let newStr: string | null = null; let actualIsSuccess = isSuccess; let actualToolTimestamp = toolTimestamp; let actualAssistantTimestamp = assistantTimestamp; const assistantNewFormat = extractFromNewFormat(assistantContent); const toolNewFormat = extractFromNewFormat(toolContent); if (assistantNewFormat.filePath || assistantNewFormat.oldStr || assistantNewFormat.newStr) { filePath = assistantNewFormat.filePath; oldStr = assistantNewFormat.oldStr; newStr = assistantNewFormat.newStr; if (assistantNewFormat.success !== undefined) { actualIsSuccess = assistantNewFormat.success; } if (assistantNewFormat.timestamp) { actualAssistantTimestamp = assistantNewFormat.timestamp; } } else if (toolNewFormat.filePath || toolNewFormat.oldStr || toolNewFormat.newStr) { filePath = toolNewFormat.filePath; oldStr = toolNewFormat.oldStr; newStr = toolNewFormat.newStr; if (toolNewFormat.success !== undefined) { actualIsSuccess = toolNewFormat.success; } if (toolNewFormat.timestamp) { actualToolTimestamp = toolNewFormat.timestamp; } } else { // Fall back to legacy format extraction const assistantLegacy = extractFromLegacyFormat(assistantContent, extractToolData, extractFilePath, extractStrReplaceContent); const toolLegacy = extractFromLegacyFormat(toolContent, extractToolData, extractFilePath, extractStrReplaceContent); // Use assistant content first, then tool content as fallback filePath = assistantLegacy.filePath || toolLegacy.filePath; oldStr = assistantLegacy.oldStr || toolLegacy.oldStr; newStr = assistantLegacy.newStr || toolLegacy.newStr; } // Additional legacy extraction for edge cases if (!filePath) { filePath = extractFilePath(assistantContent) || extractFilePath(toolContent); } if (!oldStr || !newStr) { const assistantStrReplace = extractStrReplaceContent(assistantContent); const toolStrReplace = extractStrReplaceContent(toolContent); oldStr = oldStr || assistantStrReplace.oldStr || toolStrReplace.oldStr; newStr = newStr || assistantStrReplace.newStr || toolStrReplace.newStr; } const toolTitle = getToolTitle(name); // Generate diff data (only if we have both strings) const lineDiff = oldStr && newStr ? generateLineDiff(oldStr, newStr) : []; const charDiff = oldStr && newStr ? generateCharDiff(oldStr, newStr) : []; // Calculate stats on changes const stats: DiffStats = calculateDiffStats(lineDiff); // Check if we should show error state (only when not streaming and we have content but can't extract strings) const shouldShowError = !isStreaming && (!oldStr || !newStr) && (assistantContent || toolContent); return (
{toolTitle}
{!isStreaming && ( {actualIsSuccess ? ( ) : ( )} {actualIsSuccess ? 'Replacement completed' : 'Replacement failed'} )} {isStreaming && ( Processing replacement )}
{isStreaming ? ( ) : shouldShowError ? ( ) : (
{filePath || 'Unknown file'}
{stats.additions}
{stats.deletions}

{expanded ? 'Collapse' : 'Expand'}

{expanded && (
setViewMode(v as 'unified' | 'split')} className="w-auto">
Unified Split
)}
)}
{!isStreaming && (
{actualIsSuccess ? ( ) : ( )} {actualIsSuccess ? 'String replacement successful' : 'String replacement failed'}
)} {isStreaming && (
Processing replacement...
)}
{actualToolTimestamp && !isStreaming ? formatTimestamp(actualToolTimestamp) : actualAssistantTimestamp ? formatTimestamp(actualAssistantTimestamp) : ''}
); }