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 }) => (
| Removed |
Added |
{lineDiff.map((line, i) => (
{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 (
{!isStreaming && (
{actualIsSuccess ? (
) : (
)}
{actualIsSuccess ? 'Replacement completed' : 'Replacement failed'}
)}
{isStreaming && (
Processing replacement
)}
{isStreaming ? (
) : shouldShowError ? (
) : (
{filePath || 'Unknown file'}
{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)
: ''}
);
}