Files
paseo/packages/app/src/components/tool-call-bottom-sheet.tsx
T
Mohamed BoudraandClaude 3483bd9afd fix: improve realtime controls layout and add agent status bar
- Change realtime controls to horizontal layout with fixed height (88px)
- Add orientation prop to VolumeMeter for horizontal/vertical modes
- Create AgentStatusBar component showing mode badge and agent status
- Add agent persistence system for managing agent state across restarts
- Fix bottom sheet dismiss handling to clear state properly
- Remove debug timer from realtime controls

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-24 11:17:05 +02:00

224 lines
6.2 KiB
TypeScript

import React, { useCallback, useMemo } from "react";
import { View, Text } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { ScrollView } from "react-native-gesture-handler";
import { StyleSheet } from "react-native-unistyles";
import {
BottomSheetModal,
BottomSheetView,
BottomSheetScrollView,
BottomSheetBackdrop,
BottomSheetBackdropProps,
} from "@gorhom/bottom-sheet";
import type { SelectedToolCall } from "@/types/shared";
interface ToolCallBottomSheetProps {
bottomSheetRef: React.RefObject<BottomSheetModal | null>;
selectedToolCall: SelectedToolCall | null;
onDismiss: () => void;
}
export function ToolCallBottomSheet({
bottomSheetRef,
selectedToolCall,
onDismiss,
}: ToolCallBottomSheetProps) {
const insets = useSafeAreaInsets();
const snapPoints = useMemo(() => ["80%"], []);
const renderBackdrop = useCallback(
(props: BottomSheetBackdropProps) => (
<BottomSheetBackdrop
{...props}
disappearsOnIndex={-1}
appearsOnIndex={0}
opacity={0.5}
/>
),
[]
);
// Extract data based on source
const { toolName, args, result, error } = useMemo(() => {
if (!selectedToolCall) {
return {
toolName: "Tool Call",
args: undefined,
result: undefined,
error: undefined,
};
}
const { payload } = selectedToolCall;
if (payload.source === "acp") {
const data = payload.data;
const content = data.content
?.flatMap((item) => {
if (item.type === "content" && item.content.type === "text") {
return [item.content.text];
}
return [];
})
.join("\n");
return {
toolName: data.kind ?? "Unknown Tool",
args: data.rawInput,
result: content,
error: undefined, // ACP doesn't have a separate error field
};
} else {
// Orchestrator tool call
const data = payload.data;
return {
toolName: data.toolName,
args: data.arguments,
result: data.result,
error: data.error,
};
}
}, [selectedToolCall]);
return (
<BottomSheetModal
ref={bottomSheetRef}
snapPoints={snapPoints}
backdropComponent={renderBackdrop}
enablePanDownToClose={true}
handleIndicatorStyle={styles.handleIndicator}
backgroundStyle={styles.background}
topInset={insets.top}
onDismiss={onDismiss}
>
{/* Header */}
<View style={styles.header}>
<Text style={styles.toolName}>{toolName || "Tool Call"}</Text>
</View>
{/* Scrollable content */}
<BottomSheetScrollView
contentContainerStyle={styles.sheetContent}
showsVerticalScrollIndicator={true}
>
{/* Content sections */}
{args !== undefined && (
<View style={styles.section}>
<Text style={styles.sectionTitle}>Arguments</Text>
<ScrollView
horizontal
style={styles.jsonContainer}
contentContainerStyle={styles.jsonContent}
showsHorizontalScrollIndicator={true}
nestedScrollEnabled={true}
>
<Text style={styles.jsonText}>
{JSON.stringify(args, null, 2)}
</Text>
</ScrollView>
</View>
)}
{result !== undefined && (
<View style={styles.section}>
<Text style={styles.sectionTitle}>Result</Text>
<ScrollView
horizontal
style={styles.jsonContainer}
contentContainerStyle={styles.jsonContent}
showsHorizontalScrollIndicator={true}
nestedScrollEnabled={true}
>
<Text style={styles.jsonText}>
{typeof result === "string"
? result
: JSON.stringify(result, null, 2)}
</Text>
</ScrollView>
</View>
)}
{error !== undefined && (
<View style={styles.section}>
<Text style={styles.sectionTitle}>Error</Text>
<ScrollView
horizontal
style={[styles.jsonContainer, styles.errorContainer]}
contentContainerStyle={styles.jsonContent}
showsHorizontalScrollIndicator={true}
nestedScrollEnabled={true}
>
<Text style={[styles.jsonText, styles.errorText]}>
{JSON.stringify(error, null, 2)}
</Text>
</ScrollView>
</View>
)}
</BottomSheetScrollView>
</BottomSheetModal>
);
}
const styles = StyleSheet.create((theme) => ({
handleIndicator: {
backgroundColor: theme.colors.border,
},
background: {
backgroundColor: theme.colors.popover,
},
header: {
paddingHorizontal: theme.spacing[6],
paddingTop: theme.spacing[1],
paddingBottom: theme.spacing[4],
borderBottomWidth: theme.borderWidth[1],
borderBottomColor: theme.colors.border,
backgroundColor: theme.colors.popover,
},
toolName: {
fontSize: theme.fontSize.xl,
fontWeight: theme.fontWeight.semibold,
color: theme.colors.popoverForeground,
},
sheetContent: {
paddingTop: theme.spacing[6],
paddingBottom: theme.spacing[6],
},
section: {
marginBottom: theme.spacing[6],
paddingHorizontal: theme.spacing[6],
},
sectionTitle: {
fontSize: theme.fontSize.sm,
fontWeight: theme.fontWeight.semibold,
color: theme.colors.mutedForeground,
marginBottom: theme.spacing[2],
textTransform: "uppercase" as const,
letterSpacing: 0.5,
},
jsonContainer: {
backgroundColor: theme.colors.background,
borderRadius: theme.borderRadius.lg,
borderWidth: theme.borderWidth[1],
borderColor: theme.colors.border,
// Natural height based on content
},
jsonContent: {
padding: theme.spacing[3],
},
jsonText: {
fontFamily: "monospace",
fontSize: theme.fontSize.sm,
color: theme.colors.foreground,
lineHeight: 20,
// Text maintains whitespace and formatting
},
errorContainer: {
borderColor: theme.colors.destructive,
backgroundColor: theme.colors.background,
},
errorText: {
color: theme.colors.destructive,
},
}));