import React, { useMemo, ReactNode } from "react";
import { View, Text, Platform, ScrollView as RNScrollView } from "react-native";
import { ScrollView as GHScrollView } from "react-native-gesture-handler";
import { StyleSheet } from "react-native-unistyles";
import { Fonts } from "@/constants/theme";
import {
buildLineDiff,
parseUnifiedDiff,
type ToolCallDetail,
} from "@/utils/tool-call-parsers";
import { DiffViewer } from "./diff-viewer";
import { getCodeInsets } from "./code-insets";
const ScrollView = Platform.OS === "web" ? RNScrollView : GHScrollView;
// ---- Content Component ----
interface ToolCallDetailsContentProps {
detail: ToolCallDetail;
errorText?: string;
maxHeight?: number;
}
export function ToolCallDetailsContent({
detail,
errorText,
maxHeight = 300,
}: ToolCallDetailsContentProps) {
// Compute diff lines for edit type
const diffLines = useMemo(() => {
if (detail.type !== "edit") return undefined;
// Use pre-computed unified diff if available (e.g., from apply_patch)
if (detail.unifiedDiff) {
return parseUnifiedDiff(detail.unifiedDiff);
}
return buildLineDiff(detail.oldString, detail.newString);
}, [detail]);
const sections: ReactNode[] = [];
const isFullBleed = detail.type === "edit" || detail.type === "shell";
const codeBlockStyle = isFullBleed ? styles.fullBleedBlock : styles.diffContainer;
if (detail.type === "shell") {
const command = detail.command.replace(/\n+$/, "");
const output = detail.output.replace(/^\n+/, "");
const hasOutput = output.length > 0;
sections.push(
$
{command}
{hasOutput ? `\n\n${output}` : ""}
);
} else if (detail.type === "edit") {
sections.push(
{diffLines ? (
) : null}
);
} else if (detail.type === "read") {
sections.push(
{(detail.offset !== undefined || detail.limit !== undefined) ? (
{detail.offset !== undefined ? `Offset: ${detail.offset}` : ""}
{detail.offset !== undefined && detail.limit !== undefined ? " • " : ""}
{detail.limit !== undefined ? `Limit: ${detail.limit}` : ""}
) : null}
{detail.content ? (
{detail.content}
) : null}
);
} else if (detail.type === "thinking") {
// Thinking: display the content as plain text
sections.push(
{detail.content}
);
} else {
// Generic tool: show input/output as key-value pairs
if (detail.input.length > 0) {
sections.push(
Input
);
detail.input.forEach((pair, index) => {
sections.push(
{pair.key}
{pair.value}
);
});
}
if (detail.output.length > 0) {
sections.push(
Output
);
detail.output.forEach((pair, index) => {
sections.push(
{pair.key}
{pair.value}
);
});
}
}
// Always show errors if available
if (errorText) {
sections.push(
Error
{errorText}
);
}
if (sections.length === 0) {
return (
No additional details available
);
}
return (
{sections}
);
}
// ---- Styles ----
const styles = StyleSheet.create((theme) => {
const insets = getCodeInsets(theme);
return {
paddedContainer: {
gap: theme.spacing[4],
padding: theme.spacing[2],
},
fullBleedContainer: {
gap: theme.spacing[2],
padding: 0,
},
groupHeader: {
flexDirection: "row",
alignItems: "center",
gap: theme.spacing[2],
paddingBottom: theme.spacing[1],
borderBottomWidth: theme.borderWidth[1],
borderBottomColor: theme.colors.border,
},
groupHeaderText: {
color: theme.colors.primary,
fontSize: theme.fontSize.sm,
fontWeight: theme.fontWeight.bold,
textTransform: "uppercase",
letterSpacing: 1,
},
section: {
gap: theme.spacing[2],
},
sectionTitle: {
color: theme.colors.foregroundMuted,
fontSize: theme.fontSize.xs,
fontWeight: theme.fontWeight.semibold,
textTransform: "uppercase",
letterSpacing: 0.5,
},
rangeText: {
color: theme.colors.foregroundMuted,
fontSize: theme.fontSize.xs,
},
diffContainer: {
borderWidth: theme.borderWidth[1],
borderColor: theme.colors.border,
borderRadius: theme.borderRadius.base,
overflow: "hidden",
backgroundColor: theme.colors.surface2,
},
fullBleedBlock: {
borderWidth: 0,
borderRadius: 0,
overflow: "hidden",
backgroundColor: theme.colors.surface1,
},
codeVerticalScroll: {},
codeVerticalContent: {
flexGrow: 1,
paddingBottom: insets.extraBottom,
},
codeHorizontalContent: {
paddingRight: insets.extraRight,
},
codeLine: {
minWidth: "100%",
paddingHorizontal: insets.padding,
paddingVertical: insets.padding,
},
scrollArea: {
borderWidth: theme.borderWidth[1],
borderColor: theme.colors.border,
borderRadius: theme.borderRadius.base,
backgroundColor: theme.colors.surface2,
},
scrollContent: {
padding: insets.padding,
},
scrollText: {
fontFamily: Fonts.mono,
fontSize: theme.fontSize.xs,
color: theme.colors.foreground,
lineHeight: 18,
},
shellPrompt: {
color: theme.colors.foregroundMuted,
},
jsonScroll: {
borderWidth: theme.borderWidth[1],
borderColor: theme.colors.border,
borderRadius: theme.borderRadius.base,
backgroundColor: theme.colors.surface2,
},
jsonScrollError: {
borderColor: theme.colors.destructive,
},
jsonContent: {
padding: insets.padding,
},
errorText: {
color: theme.colors.destructive,
},
emptyStateText: {
color: theme.colors.foregroundMuted,
fontSize: theme.fontSize.sm,
fontStyle: "italic",
},
};
});