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 { BottomSheetScrollView, BottomSheetBackdrop, BottomSheetBackgroundProps, } from "@gorhom/bottom-sheet"; import { X } from "lucide-react-native"; import type { ToolCallDetail } from "@server/server/agent/agent-sdk-types"; import { IsolatedBottomSheetModal, type IsolatedBottomSheetModalRef, } from "@/components/ui/isolated-bottom-sheet-modal"; import { resolveToolCallIcon } from "@/utils/tool-call-icon"; import { ToolCallDetailsContent } from "./tool-call-details"; // ----- Types ----- export type ToolCallSheetData = { toolName: string; displayName: string; summary?: string; detail?: ToolCallDetail; errorText?: string; showLoadingSkeleton?: boolean; }; 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; } // ----- 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 { theme } = useUnistyles(); const bottomSheetRef = useRef(null); const hasPresentedRef = useRef(false); const [sheetData, setSheetData] = React.useState(null); const snapPoints = useMemo(() => ["60%", "95%"], []); const openToolCall = useCallback((data: ToolCallSheetData) => { setSheetData(data); if (hasPresentedRef.current) { requestAnimationFrame(() => bottomSheetRef.current?.snapToIndex(0)); return; } hasPresentedRef.current = true; bottomSheetRef.current?.present(); }, []); const closeToolCall = useCallback(() => { bottomSheetRef.current?.close(); }, []); 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 { theme } = useUnistyles(); const { toolName, displayName, detail, errorText, showLoadingSkeleton } = data; const IconComponent = resolveToolCallIcon(toolName, detail); return ( {/* Header */} {displayName} {/* Content */} ); } // ----- Styles ----- const styles = StyleSheet.create((theme) => ({ 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, }, headerTitle: { fontSize: theme.fontSize.lg, fontWeight: theme.fontWeight.semibold, color: theme.colors.foreground, flex: 1, }, closeButton: { padding: theme.spacing[2], }, content: { flex: 1, minHeight: 0, backgroundColor: theme.colors.surface2, }, contentContainer: { padding: 0, flexGrow: 1, }, }));