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", }, }; });