import React, { createContext, useContext, useCallback, useMemo, useRef, ReactNode, } from "react"; import { View, Text, Pressable } from "react-native"; import { StyleSheet, useUnistyles } from "react-native-unistyles"; import Animated from "react-native-reanimated"; import { BottomSheetModal, BottomSheetScrollView, BottomSheetBackdrop, BottomSheetBackgroundProps, } from "@gorhom/bottom-sheet"; import { Pencil, Eye, SquareTerminal, Search, Wrench, X } from "lucide-react-native"; import { extractPrincipalParam } from "@/utils/tool-call-parsers"; import { ToolCallDetailsContent, useToolCallDetails } from "./tool-call-details"; // ----- Types ----- export interface ToolCallSheetData { toolName: string; kind?: string; status?: "executing" | "completed" | "failed"; cwd?: string; args?: unknown; result?: unknown; error?: unknown; } interface ToolCallSheetContextValue { openToolCall: (data: ToolCallSheetData) => void; closeToolCall: () => void; } // ----- Context ----- const ToolCallSheetContext = createContext(null); export function useToolCallSheet(): ToolCallSheetContextValue { const context = useContext(ToolCallSheetContext); if (!context) { throw new Error("useToolCallSheet must be used within a ToolCallSheetProvider"); } return context; } // ----- Icon Mapping ----- const toolKindIcons: Record> = { edit: Pencil, read: Eye, execute: SquareTerminal, search: Search, }; // ----- Custom Background Component ----- function CustomSheetBackground({ style }: BottomSheetBackgroundProps) { const { theme } = useUnistyles(); const containerStyle = useMemo( () => [style, { backgroundColor: theme.colors.surface2, borderRadius: 16 }], [style, theme.colors.surface2] ); return ; } // ----- Provider Component ----- interface ToolCallSheetProviderProps { children: ReactNode; } export function ToolCallSheetProvider({ children }: ToolCallSheetProviderProps) { const bottomSheetRef = useRef(null); const [sheetData, setSheetData] = React.useState(null); const snapPoints = useMemo(() => ["60%", "95%"], []); const openToolCall = useCallback((data: ToolCallSheetData) => { setSheetData(data); bottomSheetRef.current?.present(); }, []); const closeToolCall = useCallback(() => { bottomSheetRef.current?.dismiss(); }, []); const handleSheetChange = useCallback((index: number) => { if (index === -1) { setSheetData(null); } }, []); const renderBackdrop = useCallback( (props: React.ComponentProps) => ( ), [] ); const contextValue = useMemo( () => ({ openToolCall, closeToolCall }), [openToolCall, closeToolCall] ); return ( {children} {sheetData && } ); } // ----- Sheet Content Component ----- interface ToolCallSheetContentProps { data: ToolCallSheetData; onClose: () => void; } function ToolCallSheetContent({ data, onClose }: ToolCallSheetContentProps) { const { toolName, kind, cwd, args, result, error } = data; const IconComponent = kind ? toolKindIcons[kind.toLowerCase()] || Wrench : Wrench; const { display, errorText } = useToolCallDetails({ toolName, args, result, error }); const principalParam = useMemo(() => extractPrincipalParam(args, cwd), [args, cwd]); return ( {/* Header */} {display.toolName} {principalParam ? ( {principalParam} ) : null} {/* Content */} ); } // ----- Styles ----- const styles = StyleSheet.create((theme) => ({ sheetBackground: { backgroundColor: theme.colors.surface2, }, handleIndicator: { backgroundColor: theme.colors.palette.zinc[600], }, container: { flex: 1, backgroundColor: theme.colors.surface2, }, header: { flexDirection: "row", alignItems: "center", justifyContent: "space-between", paddingHorizontal: theme.spacing[4], paddingVertical: theme.spacing[3], borderBottomWidth: theme.borderWidth[1], borderBottomColor: theme.colors.border, }, headerLeft: { flexDirection: "row", alignItems: "center", gap: theme.spacing[2], flex: 1, }, headerTextColumn: { flex: 1, minWidth: 0, }, headerIcon: { color: theme.colors.foreground, }, headerTitle: { fontSize: theme.fontSize.lg, fontWeight: theme.fontWeight.semibold, color: theme.colors.foreground, flex: 1, }, headerSubtitle: { marginTop: theme.spacing[1], fontSize: theme.fontSize.sm, color: theme.colors.foregroundMuted, }, closeButton: { padding: theme.spacing[2], }, closeIcon: { color: theme.colors.foregroundMuted, }, content: { flex: 1, backgroundColor: theme.colors.surface2, }, contentContainer: { padding: 0, }, }));